Nginx 502 和 504 不是一回事:一个说错话,一个不说话

举报
yd_282927746 发表于 2026/11/13 08:12:48 2026/11/13
【摘要】 502 是连上了但 upstream 说了 Nginx 不认识的话,504 是连接建立了却等不到回包。靠 upstream_status 和 upstream_response_time 就能定责:前者区分"上游返回的 502"与"连接失败",后者区分后端真慢还是 Nginx 排队。附四类 502 成因、三层超时的正确理解、可执行排查清单,以及一个 keepalive 未对齐导致低峰期偶发 502

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 分开看,绝大多数"网关超时"半小时内就能定位到责任人。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。