Nginx 502 和 504 不是一回事:一个说错话,一个不说话
502 和 504,不是同一个"超时"
线上报错,有人截图说"网关超时了,504"。翻日志发现一半是 502。这两个码经常被混着说,但它们的成因完全不同,改错方向就是在浪费时间。
先把结论摆出来:
- 502 Bad Gateway:Nginx 连上了 upstream,但 upstream 返回了一个 Nginx 不认识的响应(连接被重置、响应头非法、空响应)。
- 504 Gateway Timeout:Nginx 等 upstream 响应超时了 —— 连接建立了,请求发出去了,就是没等到回包。
一句话区分:502 是"对方说错话",504 是"对方不说话"。
这个区分决定了排查方向:502 要去看 upstream 进程本身(崩了?被杀了?返回了什么),504 要去看 upstream 的耗时(慢在哪一段?是不是 Nginx 等得不够久?)。
一、先确认是谁返回的
Nginx 错误日志里,关键字段是 upstream_status 和 upstream_addr:
upstream: "http://10.0.1.5:8080/api/x", host: "a.example.com",
request: "GET /api/x HTTP/1.1", upstream_status: "502",
upstream_addr: "10.0.1.5:8080",
request_time: 0.012
upstream_status: 502 表示这个 502 是 upstream 自己返回的(应用里抛的),upstream_status: - 表示 Nginx 没能从 upstream 拿到状态行(连接失败/重置)。
这个区别很重要:
upstream_status: 502→ 应用层的问题(比如 PHP-FPM 崩了、Tomcat 抛了 502、容器 OOM 被杀);upstream_status: -且upstream_addr有值 → 连接建立后对端直接断(进程退出、keepalive 连接被上游关闭);- 错误日志里
connect() failed (111: Connection refused)→ upstream 端口没人监听。
二、502 的四种典型成因
① 上游应用进程崩了
最直接:connect() failed。看应用日志有没有 OOM、panic、segfault。容器环境下 kubectl describe pod 看 Last State: Terminated, Reason: OOMKilled。
② 上游应用自己返回 502
有些框架(PHP-FPM、Tomcat + mod_jk、部分网关)在内部错误时会返回 502。此时 upstream_status 就是 502,需要看应用日志。
经典案例:PHP-FPM 的 pm.max_children 满了,FPM 返回 "server reached pm.max_children setting" 并直接断开,Nginx 侧就是 502。查 pm.max_children、pm.max_requests,以及 listen.backlog。
③ 响应头非法或响应体截断
Nginx 严格校验 HTTP 响应。如果上游返回的 header 里带了非法字符、header 太长(超过 large_client_header_buffers)、或者 Content-Length 与实体长度不一致,Nginx 会判定为非法响应并返回 502,日志里常见:
upstream sent invalid header ... while reading response header from upstream
upstream prematurely closed connection while reading response header
④ keepalive 连接被上游提前关闭
配了 keepalive 但上游的超时比 Nginx 短,就会出现"拿到一条已经死掉的连接":
upstream prematurely closed connection while reading response header
Nginx 默认会重试(因为 proxy_next_upstream 默认包含 error timeout),但重试只对有幂等性的请求安全。关键是让两端 keepalive 超时对齐:上游的 idle timeout 要比 Nginx 的 keepalive_timeout 长一点。
三、504 的三层时间
504 只关心一件事:Nginx 等了多少秒,超时阈值是多少。
proxy_connect_timeout 5s; # 建连超时
proxy_send_timeout 60s; # 两次写之间的间隔
proxy_read_timeout 60s; # 两次读之间的间隔(最常被误解)
最容易搞错的是 proxy_read_timeout:它不是"整个请求的总时长",而是"两次读操作之间的最大间隔"。
一个流式接口,每 10 秒吐一次数据,总时长 5 分钟,proxy_read_timeout 60s 是没问题的 —— 因为每次数据到达都会重置计时器。反过来,一个请求卡在后端 61 秒才吐第一个字节,哪怕它最终成功,也会 504。
所以 504 的第一件事是分清:
- 后端真的慢(慢 SQL、外部依赖超时、锁等待);
- 后端没慢但 Nginx 等得不够久(长任务被误配了短超时)。
判断方法:对比 Nginx 的 request_time 和应用自己记录的耗时。如果应用日志显示 3 秒完成,Nginx 却是 504,那就是 Nginx 到应用之间有问题(连接池耗尽、队列积压)。
四、几个"看起来是超时,其实不是"的场景
① upstream 队列满了
upstream backend {
server 10.0.1.5:8080 max_conns=100;
queue 200 timeout=10s;
}
queue 满了之后请求在 Nginx 侧排队,等不到槽位直接 504(queue timeout)。这时候后端很闲,全是 Nginx 在排队。
② proxy_next_upstream 重试放大延迟
默认重试一次。如果两台后端都慢,实际耗时会是单台的 2 倍。proxy_next_upstream_tries 太大时,用户等待被成倍放大。
③ gzip 后的慢客户端
响应体大、客户端网络差,写回客户端时慢。这不会 504(504 只针对 upstream),但会表现为 request_time 很长。
④ 连接池耗尽导致的假超时
keepalive 32 配小了,高并发下 Nginx 不停新建到 upstream 的连接,TIME_WAIT 堆积。表现为延迟抖动 + 偶发 502/504 混杂。ss -s 里 timewait 数量是重要指标。
五、一份可执行的排查清单
502:
# 1. 上游活着吗
curl -sv http://10.0.1.5:8080/health --max-time 3
# 2. 错误日志的精确原因
grep " 502 " /var/log/nginx/error.log | tail -50
# 3. 上游进程
systemctl status <app>; dmesg -T | grep -i "killed process"
504:
# 1. 是不是某一类接口
awk '{print $7}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -20
# 2. 慢请求的耗时分布
grep " 504 " /var/log/nginx/access.log | awk '{print $NF}' | sort -n | tail -20
# 3. upstream 侧是否也慢
grep "upstream_response_time" /var/log/nginx/error.log | tail -20
upstream_response_time 是判断责任归属的关键:如果它接近 0 而 request_time 很大,问题在 Nginx 内部或客户端方向;如果它也很大,问题在后端。
六、调优时最容易犯的三个错
① 无脑把 proxy_read_timeout 调到 600s
调大超时的本质是"让用户多等一会儿",它不解决任何性能问题,只是把错误从 504 变成"页面转圈 10 分钟"。正确做法是:给接口分等级,普通接口 5~10s,确有必要的大任务走异步 + 轮询/回调。
② 用 proxy_next_upstream 掩盖 502
重试能救偶发的连接抖动,但如果上游持续崩,重试只是把 N 次失败摊到用户身上。看 upstream_addr 里是否出现多个地址就能发现。
③ 忽略 upstream_keepalive 的对齐
Nginx 的 keepalive_timeout 和上游框架的 idle timeout 不一致,会产生"连接被复用但已关闭"的 502。表现为低负载下偶发 502,高负载反而正常(因为连接来不及复用)。
七、一个真实的定位过程
现象:99% 的请求正常,每分钟有 2~3 个 502,集中在夜间低峰。
第一步看日志分布:全部是 upstream prematurely closed connection while reading response header,upstream_status: -。
第二步看 upstream 配置:keepalive 64,keepalive_timeout 75s。
第三步看应用:Tomcat 的 connectionTimeout 是 20s。
应用 20 秒就关了空闲连接,Nginx 还以为是活的,75 秒内继续复用。低峰期连接空闲时间长,正好命中这个窗口 —— 所以是"低负载更容易出问题"。
修法是把应用侧调到 90s(比 Nginx 长),或者 Nginx 侧调到 15s(比应用短)。之后 502 归零。
这个案例说明:502 不一定有凶手,往往只是两边超时没对齐。
小结
| 现象 | 含义 | 先查什么 |
|---|---|---|
502 + connect() failed |
上游没监听 | 进程是否存活 |
502 + invalid header |
响应不合法 | 上游返回内容、large_client_header_buffers |
502 + prematurely closed |
连接被复用但已死 | keepalive 超时是否对齐 |
504 + upstream_response_time 大 |
后端真慢 | 应用日志、慢 SQL、外部依赖 |
504 + upstream_response_time 小 |
后端没事 | Nginx 排队、max_conns、queue |
把 502 和 504 分开看,把 request_time 和 upstream_response_time 分开看,绝大多数"网关超时"半小时内就能定位到责任人。
- 点赞
- 收藏
- 关注作者
评论(0)