HTTP 5xx 归因:502/503/504 分别由谁产生、该看哪一层日志、怎么用 curl -v 定位
三个 5xx 的分工很清楚:502 和 504 由网关(nginx)产生,区别在于它是"连不上上游"还是"等不到上游";503 既可能由网关产生(限流、没有可用上游),也可能就是上游自己返回的。归因的第一步不是看应用日志,而是确认这个响应是谁写的——curl -v 里的 Server 头和响应体特征就能回答。
谁产生、看哪层日志
| 状态码 | 产生者 | error_log 里的典型原文 | 特征 |
|---|---|---|---|
| 502 Bad Gateway | 网关 | connect() failed (111: Connection refused) while connecting to upstream | 极快返回(毫秒级),因为连接被拒或上游提前断开 |
| 504 Gateway Timeout | 网关 | upstream timed out (110: Connection timed out) while reading response header from upstream | 耗时正好等于配置的超时值 |
| 503 Service Unavailable | 网关或上游 | limiting requests, excess: 1.000 by zone "rl" | 网关产生时页面带 nginx 版本号 |
| 500 Internal Server Error | 上游(通常) | 网关侧一般无 error 行 | 上游自己的堆栈/日志里能找到 |
实测环境:本机 nginx 1.22.1,三个 location 分别指向关闭的端口、不返回数据的上游、以及 limit_req 限流区。
/r502 -> HTTP 502 in 0.000269s
/r504 -> HTTP 504 in 1.002511s
/r503 #1 -> HTTP 200
/r503 #2 -> HTTP 503
/r503 #3 -> HTTP 503
对应的 error.log(同一份文件,三行):
[error] *2 connect() failed (111: Connection refused) while connecting to upstream,
request: "GET /r502 HTTP/1.1", upstream: "http://127.0.0.1:9/r502"
[error] *4 upstream timed out (110: Connection timed out) while reading response header
from upstream, request: "GET /r504 HTTP/1.1", upstream: "http://127.0.0.1:8081/r504"
[error] *7 limiting requests, excess: 1.000 by zone "rl", request: "GET /r503 HTTP/1.1"
access.log 同步记下了状态码和响应字节数,可以用来对齐:
"GET /r502 HTTP/1.1" 502 157
"GET /r504 HTTP/1.1" 504 167
"GET /r503 HTTP/1.1" 503 197
用 curl -v 定位
$ curl -sv --max-time 5 http://127.0.0.1:8080/r502
* Trying 127.0.0.1:8080...
* Connected to 127.0.0.1 (127.0.0.1) port 8080 (#0)
> GET /r502 HTTP/1.1
> Host: 127.0.0.1:8080
< HTTP/1.1 502 Bad Gateway
< Server: nginx/1.22.1
< Content-Type: text/html
< Content-Length: 157
要看的就四行:Connected to 确认连的是哪一跳(排除 DNS 或 VIP 选错)、> GET 确认请求行和 Host、< HTTP/1.1 502 确认状态码来源、< Server: 确认这个响应是谁写的。Content-Length: 157 与 access.log 的 157 对得上,说明我看到的页面就是 nginx 生成的那个,不是上游转发的。
需要绕过负载均衡直接打某一台后端时,用 --resolve 固定解析,避免结论被 DNS 轮询干扰:
curl -sv --resolve app.example.com:443:10.0.0.5 https://app.example.com/health
curl -s -o /dev/null -w 'code=%{http_code} connect=%{time_connect} start=%{time_starttransfer} total=%{time_total}\n' \
https://app.example.com/
-w 的四个耗时是区分 502 与 504 最便宜的手段:time_connect 极短而状态码 502,基本就是连接被拒;time_starttransfer 恰好等于 proxy_read_timeout,基本就是 504。
三个坑
第一,把 502 当超时排查。本次 502 的耗时是 0.000269 秒,connect() failed (111: Connection refused) 说明上游端口根本没在监听,请求连 TCP 握手都没完成。这种情况下调大 proxy_read_timeout 毫无作用,应该去查上游进程是否活着、端口是否改过、容器是否在另一个网络命名空间里。
第二,503 的来源看响应体。nginx 自己生成的 503 页面长这样:
<html>
<head><title>503 Service Temporarily Unavailable</title></head>
<body><center><h1>503 Service Temporarily Unavailable</h1></center>
<hr><center>nginx/1.22.1</center></body>
</html>
末尾带版本号是典型特征。如果 503 的响应体是业务 JSON、或者带应用自己的 header,那它就是上游返回的,要去应用日志里找原因,而不是查限流配置。
第三,proxy_next_upstream 会掩盖单台后端故障。默认情况下上游返回 502/504 或连接失败时,nginx 会把请求转给下一台,客户端看到的可能仍是 200,只有 error_log 里留下一条 upstream 记录。排查"偶发慢"时要同时看 access_log 的 upstream_status(需要在 log_format 里显式加上)和 error_log,只看客户端状态码会漏掉整台已经坏掉的后端。
多级网关与容器化上游
上游不是单机而是另一层网关时,502 与 504 会在链路中间被"翻译"一次:客户端看到的是最外层 nginx 生成的 502,而真实原因可能是内层网关连不上应用。判断方法是逐跳看:curl -v 的 Server 头告诉你最外层是谁,error_log 里的 upstream: 地址告诉你它把请求转给了谁,顺着这个地址再打一次同样的路径,就能定位到具体哪一跳断掉。
上游在容器里时还有一个高频原因:容器重启后 IP 变了,而 nginx 在启动时解析了上游域名并缓存。表现为"容器是健康的,nginx 一直 502"。用 resolver 配合变量式 proxy_pass,或者把上游写成 Docker 网络里的服务名并定期 nginx -s reload,都能缓解;前者是官方推荐做法,代价是需要在 location 里显式设置变量。
还有一类容易误判的情况:500 与 502 混在一起出现。500 通常是上游应用自己抛出的,nginx 的 error_log 里不会出现 upstream 字样;502 一定会带 upstream 行。先按"有没有 upstream 行"分流,再看应用日志,能少走很多弯路。
超时参数该调哪一个
504 的根因是"等太久",但等在哪一段决定了调哪个参数。proxy_connect_timeout 管的是建立到上游的 TCP 连接,默认 60 秒且手册提示通常不超过 75 秒;proxy_read_timeout 管的是两次读之间的间隔,默认 60 秒;还有一个 proxy_send_timeout 管写请求体。上游能连上但迟迟不回包,属于 proxy_read_timeout;上游连不上、SYN 发出去没响应,属于 proxy_connect_timeout。
判断方法看 error_log 的措辞:while reading response header from upstream 是读超时,while connecting to upstream 是连接超时。两种情况调错参数都不会有效果,反而会把真正的上游性能问题掩盖得更久。
小结
归因顺序是:先用 curl -v 确认状态码与 Server 头,判断响应由网关还是上游产生;再用 time_total 区分"被拒"与"超时";最后到对应层的日志里找原文。502 查上游可达性,504 查上游处理时长与超时配置,503 先分清是限流还是上游返回。
定位过程中保留原始证据很重要:error_log 的那一行包含 upstream: 地址和 request: 行,access.log 包含状态码与字节数,两者对齐后可以确定是哪一个请求、哪一台上游、哪一类失败。把这些贴进工单,比"用户反馈 502"有效得多。
数据来源与检索时间
| 来源 | URL | 对应数据 |
|---|---|---|
| nginx.org · ngx_http_proxy_module | https://nginx.org/en/docs/http/ngx_http_proxy_module.html | proxy_connect_timeout 默认 60s、proxy_read_timeout 语义、proxy_next_upstream 与 proxy_cache_use_stale 可接受的错误类型(error/timeout/http_502/http_503/http_504) |
| nginx.org · ngx_http_upstream_module | https://nginx.org/en/docs/http/ngx_http_upstream_module.html | max_fails/fail_timeout 判定上游不可用、失败后转下一台、"所有服务器都失败时返回最后一台的结果" |
检索时间:2026-11-10(Asia/Shanghai)。
核验说明
- 真跑:在
debian:12-slim容器内安装 nginx 1.22.1,配置三个 location 分别构造 502、504、503,用curl打并抓取error.log与access.log。文中三行 error_log、三行 access_log、四个耗时数字(0.000269s / 1.002511s / 200 / 503)均为原始输出。 - 真跑:
curl -sv对/r502的完整输出片段(含Server: nginx/1.22.1与Content-Length: 157)为实测;503 的响应体 HTML 也是实测抓取。 - 真跑:504 的上游用"接受连接但不返回数据"的方式构造,
proxy_read_timeout 1s下耗时 1.0025s,与配置吻合。 - 仅依据文档:
proxy_next_upstream的重试行为、upstream_status需要自定义log_format才能看到,按 nginx 文档记录,未在本次实验中构造多台上游验证。 - 未验证:500 的归因(属上游应用层,文中未构造)。