Nginx 不提供 HTTP 状态码生命周期概念,$status 变量在 log 阶段才最终锁定为响应客户端的状态码,只读且代表最终结果;可通过 access_log 或 log_by_lua_block 结合 upstream_status 等变量实时捕获与归因。

Nginx 本身不提供“HTTP状态码生命周期”的内置概念——它没有记录某个 404 或 502 状态从产生、持续到消失的完整时序轨迹。所谓“单个状态码的实时生命周期”,实际是指:**在请求处理过程中,状态码何时确定、能否被后续模块修改、如何在日志/响应头/监控中准确捕获其最终值**。关键在于理解 Nginx 请求处理阶段与 $status 变量的行为边界。
明确 $status 变量的本质和生效时机
$status 是只读的内置变量,其值在请求处理的 最后一个阶段(log 阶段)才被最终锁定,代表该请求最终返回给客户端的状态码。它不是实时可变的中间量:
- 上游代理返回 502,但你用
error_page 502 =200 /fallback拦截并改写为 200,则$status记录的是 200,不是 502; - rewrite 阶段或 access 阶段的
return 403会直接终止流程,此时$status就是 403; - 如果启用了
subrequest(如 auth_request),子请求的状态码不会影响主请求的$status。
通过 access_log 实时捕获最终状态码
这是最常用且可靠的方式。利用 $status 在 log 阶段的确定性,配合自定义日志格式和外部工具实现实时监控:
- 定义仅含状态码的日志格式:
log_format status_only '$status'; - 将该格式输出到独立文件或命名管道:
access_log /var/log/nginx/status.log status_only; - 用
tail -f+awk或goaccess实时统计频次:tail -f /var/log/nginx/status.log | awk '{count[$1]++} END {for (c in count) print c, count[c]}'; - 更进一步,接入 Prometheus:用 nginx-lua-prometheus 模块,在 log_by_lua_block 中调用
metric:inc(1, ngx.var.status),实现按状态码维度的实时计数器。
用 log_by_lua_block 捕获“临界点”行为
若需感知状态码变更前的上下文(例如:识别哪些请求曾触发 upstream timeout 后又被 error_page 捕获),可在 log_by_lua_block 中结合其他变量做交叉判断:
-
upstream_status:记录 upstream 返回的原始状态码(多个 upstream 时以逗号分隔); -
upstream_http_content_type、upstream_response_length:辅助判断 upstream 是否真正返回了响应; - 示例逻辑:
if ngx.var.status == "200" and string.match(ngx.var.upstream_status, "50[24]") then ngx.log(ngx.INFO, "502/504 masked as 200 for ", ngx.var.uri) end。
避免常见误解:状态码没有“生命周期追踪”能力
Nginx 不维护状态码的历史快照,也无法回答“这个 502 是第几次出现”或“它持续了多久”。如果你需要:
- 趋势分析:聚合分钟级状态码分布(用 logrotate + logstash 或 vector 做批处理);
-
异常检测:对
$status做滑动窗口计数(如过去 60 秒内 5xx 超过 10 次),需借助 Lua + shared dict 或外部时序数据库; - 请求链路追踪:应使用 OpenTelemetry 或 Jaeger,在 upstream 调用处埋点,而非依赖 Nginx 单点状态。
不复杂但容易忽略:真正可靠的监控锚点永远是 log_by_lua_block 或 access_log 中的 $status,它代表结果,而非过程。把关注点放在“谁改变了最终状态”和“改变后如何归因”,比试图还原不存在的生命周期更有实际价值。











