ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Linux Nginx 各类 HTTP 状态码的业务含义与故障定位指南

Linux Nginx 各类 HTTP 状态码的业务含义与故障定位指南 前言排障时最没用的信息是「用户说打不开」第二没用的是一句「报 502 了」。状态码真正的价值不在于记住它的官方名称而在于它能回答一个更关键的问题这个数字是谁生成的。同样是 502可能是上游应用崩了、可能是 Nginx 与上游之间的超时策略不对、也可能是云负载均衡返回的三者的排查方向完全不同同样503 可能是后端全挂了也可能只是本机limit_req限流触发的默认响应。分不清生成方就只能靠猜。Nginx 语境下还有一层额外的复杂性Nginx 有自己的内部状态码比如 444、499它们只出现在访问日志和错误日志里客户端永远看不到标准 HTTP 中对应的含义反过来Nginx 也常常把上游的错误「吞掉」并重新包装。这套机制不搞清楚日志读起来就是自相矛盾的。本文按「谁生成、下一步查什么」的思路把 2xx 到 5xx 逐类拆开讲重点放在 4xx 和 5xx 这两个排障主战场上并给出一套可直接执行的日志分析命令。示例基于 RHEL 9 / nginx 1.24 与 Debian 12 / nginx 1.22配置语法通用日志路径默认都是/var/log/nginx/。一、先分清楚状态码是谁生成的这是整篇最重要的一张表。判断依据只有一个访问日志里$status与$upstream_status是否同时存在。组合含义首查方向$status200无$upstream_statusNginx 直接响应静态文件、return无$status502/504$upstream_status为空Nginx自己造的502/504压根没拿到上游响应上游地址是否可达、是否超时$status502$upstream_status502上游返回了502应用日志$status200$upstream_status500Nginx 用proxy_intercept_errors或error_page改了状态码上游报错但被改写去查应用$status499Nginx 独有客户端在响应发出前主动断开调大超时或查客户端行为因此一个合格的访问日志格式必须先把这个能力打开否则事后只能凭感觉猜# 基于 nginx 1.24RHEL 9 / Debian 12 通用放在 http 上下文 log_format diag $remote_addr|$time_iso8601|$request|$status|$upstream_addr| $upstream_status|$request_time|$upstream_response_time|$http_user_agent; access_log /var/log/nginx/access.log diag;用|做分隔符而不是空格是为了让awk -F|稳定取值——默认的 combined 格式里$request、$http_referer都带空格和引号靠空格切列极易错位。顺带解释 Nginx 的几个内部码它们只出现在日志里客户端看不到内部码含义客户端实际收到444return 444;直接关闭连接不发响应连接被重置499客户端提前关闭了连接——本身就是客户端行为494请求头过大large_client_header_buffers相关400495客户端证书校验失败400 或 TLS 层失败496要求客户端证书但未提供400497明文 HTTP 请求打到了 HTTPS 端口400这些码的确切取值和行为在不同版本间有过调整排障时以你所用版本的错误日志原文为准。二、2xx 与 3xx业务语义比数字更容易被搞错2xx 类最常见的问题不是「出错」而是语义用错导致上下游行为不一致状态码语义常见误用200 OK请求成功响应体有效用它来返回「失败」的业务错误让监控统计失真201 Created资源已创建通常带Location头创建接口返回 200客户端无法区分204 No Content成功但没有响应体返回 204 却还带 JSON body被客户端丢弃206 Partial Content断点续传/分片下载静态大文件下载依赖它配置max_ranges才能关闭301 Moved Permanently永久跳转可能改变请求方法用 301 做临时跳转被浏览器永久缓存302 Found临时跳转历史上也可能把 POST 变 GET同上方法语义不保证304 Not Modified协商缓存命中无响应体与 200 混在一起统计误以为缓存失效Nginx 侧真正容易踩的是 3xxrewrite ... permanent是 301rewrite ... redirect是 302。做临时跳转灰度、A/B、临时维护时误用permanent浏览器和 CDN 会把这个跳转长期缓存下来即使你改回配置老访客依然被跳到旧地址且很难让终端用户清掉。301 和 302 不保证保留请求方法。如果要把 POST 原样转发到新地址应当用 307临时或 308永久这两个码明确要求保留方法和请求体。Nginx 支持return 307 https://...;与return 308 https://...;。304 不是错误。它意味着客户端本地缓存仍然有效能极大减少带宽。做「缓存命中率」统计时要把它算作命中而不是异常。让 304 生效需要响应带Last-Modified或ETag静态文件默认就有proxy_pass的场景则取决于上游是否返回。206 是下载功能的生命线。如果下载工具报「服务器不支持断点续传」先确认请求头里带了Range再看 Nginx 是否因为proxy_set_header Range ;之类的配置把Range头吃掉了。三、4xx绝大多数是客户端问题但有几个是 Nginx 配错了4xx 的通用含义是「请求本身有问题」但在 Nginx 上下面这几个特别值得单独记状态码Nginx 侧常见成因定位方法400 Bad Request请求行/头格式非法明文 HTTP 打到 HTTPS 端口请求头超长错误日志关键词client sent invalid、plain HTTP request sent to HTTPS port401 Unauthorized未认证auth_basic、auth_request、auth_jwt响应必须带WWW-Authenticate否则客户端不知如何认证403 Forbidden已认证但无权限deny命中目录无index且禁止列目录检查allow/deny顺序与目录权限404 Not Found文件不存在root/alias路径拼错用log_not_found off;降噪别用它掩盖错误405 Method Not Allowed对静态文件用了 POST/PUT 等方法Nginx 对静态资源只接受 GET/HEAD408 Request Timeoutclient_header_timeout/client_body_timeout到点慢客户端或链路质量差409 Conflict通常由应用返回Nginx 不生成应用日志413 Request Entity Too Largeclient_max_body_size默认 1m错误日志client intended to send too large body414 / 长 URI请求行超长large_client_header_buffers相关错误日志request line is too large429 Too Many Requestslimit_req_status 429;/limit_conn_status 429;显式设置后默认这两个指令给的是503见下节431 Header Fields Too Large请求头字段过大多由上游或其他组件返回版本差异较大以实测为准两个必须记住的细节第一limit_req与limit_conn的默认返回码是 503不是 429。很多人看到 503 就去找后端实际是本机限流生效了。要让它返回语义正确的 429需要显式配置# 基于 nginx 1.24 http { limit_req_zone $binary_remote_addr zoneapi:10m rate10r/s; limit_conn_zone $binary_remote_addr zoneconn:10m; server { location /api/ { limit_req zoneapi burst20 nodelay; limit_conn conn 20; limit_req_status 429; # 默认是 503 limit_conn_status 429; # 默认是 503 proxy_pass http://127.0.0.1:8080; } } }第二401 与 403 的区别是「缺凭证」还是「凭证不足」。用auth_basic时如果只返回 403浏览器不会弹出登录框——因为弹框由WWW-Authenticate头触发而 Nginx 在 401 时才会自动加上它由auth_basic模块负责。把 401 改成 403 会让登录功能看起来「没反应」。四、5xx把责任方定位到具体的哪一层5xx 是排障主战场逐个拆开500 Internal Server ErrorNginx 本身很少主动产生 500。多数情况下是上游返回的 500若$upstream_status为空则考虑error_page循环、rewrite死循环、auth_request子请求失败等情况。501 Not Implemented请求方法不被支持。Nginx 对无法识别的方法会返回 501错误日志里常有client sent invalid method。502 Bad Gateway上游不可用或返回了无法解析的内容。典型成因后端进程没起来、端口写错、上游返回了非 HTTP 内容例如后端直接崩掉吐了一个裸 TCP 包、上游用了 HTTP/2 而 Nginx 按 HTTP/1.x 解析。若$upstream_status有值说明上游真的返回了 502。503 Service Unavailable上游全部不可用upstream里所有 server 都被标记为 down或本机限流 / 连接数限制触发。后者的典型特征是错误日志里没有上游连接失败信息访问日志里$upstream_addr为空。504 Gateway TimeoutNginx 等上游超时了对应指令是proxy_read_timeout默认 60s、proxy_connect_timeout默认 60s、proxy_send_timeout默认 60s。注意 504 与 502 的分水岭是「有没有连接上」连上了但没及时回是 504连不上或响应无法解析是 502。区分 502/504 与「上游返回 500」的最快办法是同时看$upstream_addr与$upstream_status# 统计状态码分布配合本文第一节的 log_format 使用 awk -F| {print $4} /var/log/nginx/access.log | sort | uniq -c | sort -rn # 只看 5xx并打印上游地址与上游状态码 awk -F| $4 ~ /^5/ {print $4, up$5, us$6, $3} /var/log/nginx/access.log | head -50 # 找出耗时超过 1 秒的请求按耗时倒序第 7 列是 $request_time awk -F| $7 1.0 {print $7, $4, $5, $3} /var/log/nginx/access.log | sort -rn | head -20 # 统计 5xx 的 URI 分布定位是哪个接口在崩 awk -F| $4 ~ /^5/ {split($3, a, ); print a[2]} /var/log/nginx/access.log \ | sort | uniq -c | sort -rn | head -20$request_time与$upstream_response_time的差值就是Nginx 自身的开销包括接收客户端慢请求体、TLS 握手、gzip 压缩、写响应。如果这个差值很大而上游很快问题在 Nginx 或网络不在应用# 打印「Nginx 自身耗时」超过 0.5 秒的记录 awk -F| ($7 - $8) 0.5 $8 ! {print $7, $8, $1, $3} \ /var/log/nginx/access.log | head -20还有一个只在日志里能看到的线索是499。它的含义是客户端在 Nginx 发出响应前就断开了连接常见于后端响应比用户耐心还慢用户手动刷新或关闭页面客户端设置了过短的超时移动端网络切换、App 的 5 秒超时或者上游proxy_read_timeout设得过长把慢请求都积压成了 499。看到大量 499 时排查方向应该是「后端为什么慢」而不是「Nginx 为什么报错」——因为 Nginx 没做错任何事。常见坑点❌ 只记$status日志格式里没有$upstream_status和$upstream_addr于是 502 出现后完全无法判断是 Nginx 造的还上游给的。✅ 生产环境的访问日志必须带这三个变量。这是排障能力的地基改动成本几乎为零。❌ 看到 503 就直接去重启后端。✅ 先确认$upstream_addr是否为空。为空说明请求压根没转发出去多半是limit_req/limit_conn限流或上游组全部标记为 down这两个指令的默认状态码就是 503。❌ 用error_page 502 /502.html;想让用户看到友好页面结果监控系统再也收不到 502 了。✅error_page 502 /502.html;保留原状态码只有带才改写。真正会吞掉状态码的是error_page 502 200 /502.html;这类带的写法它会返回 200务必避免。同时把proxy_intercept_errors的开启范围限制在必要 location 内。❌ 把 404 全部用error_page 404 200 /index.html;兜成 200做「前端路由回退」。✅ 这是单页应用的常见需求但正确的做法是把回退限制在实际由前端路由接管的前缀上如location /app/而不是全站。全站回退会让搜索引擎和监控都认为「任何不存在的地址都返回 200」抓取到无限量的重复页面。❌ 用 301 做临时跳转。✅ 临时跳转用 302 或 307。301 会被浏览器与 CDN 长期缓存配置回滚后老客户端仍走旧路径。❌ 需要透传 POST 方法时用 301/302发现请求体丢了。✅ 用 307/308它们要求保留方法和请求体。❌ 把 499 当成服务端错误加到告警规则里半夜被叫起来发现只是用户刷新了页面。✅ 499 是客户端行为应作为「后端响应慢」的间接指标来做趋势告警而不是错误率告警。真正该告警的是 5xx 比例与$upstream_response_time的 P95 分位。❌ 用awk {print $9}从默认 combined 格式里取状态码一旦有人给日志加了$http_x_forwarded_for字段列号就全错了。✅ 定义自定义log_format并用固定分隔符如|切分而不是依赖列位置。总结状态码谁生成第一步查什么304Nginx协商缓存属正常统计时算缓存命中400Nginx错误日志client sent invalid或明文打到 HTTPS 端口401 / 403Nginxauth_basic等是否缺少WWW-Authenticateallow/deny顺序404 / 405Nginxroot/alias路径是否正确静态资源不支持 POST413Nginxclient_max_body_size错误日志too large body429Nginx需显式配置默认限流返回的是 503检查limit_*_status499Nginx 记录客户端主动断开根因是后端慢502Nginx 或上游$upstream_addr是否为空区分「连不上」与「上游返回 502」503Nginx 或上游$upstream_addr为空则查限流与 upstream 状态504Nginx上游连上了但超时查proxy_read_timeout与后端耗时用好状态码的关键动作只有两个在日志里同时记录$status、$upstream_addr、$upstream_status以及记住 Nginx 会生成一批标准之外或语义特殊的码444、499、497、默认 503 限流。前者把「502 到底是什么」从猜测变成事实后者避免把 Nginx 自己的行为误判成后端故障。剩下的工作交给awk与上述几条统计命令就够了。各版本对内部码与默认状态码的处理可能不同最终请以你的 Nginx 版本实测结果与官方文档为准。
返回列表