Nginx 502/504 排查实录:从超时到连接池

发版后半小时,接口开始间歇性报错:一会 502 Bad Gateway,一会 504 Gateway Timeout,监控群里告警刷屏。Nginx 502/504 是线上最常见的「网关类」故障,但很多人一上来就重启,结果十分钟后又复发。本文是一份实战排查实录:先用 error log 定位方向,再区分 502 与 504 的根源,最后落到 upstream 超时与连接池两个最常被忽略的配置点。它和 慢 SQL 拖垮生产库:一次 CPU 100% 排查实录文件描述符耗尽导致服务假死:一次 fd 泄漏排查实录Java 内存泄漏排查:从监控告警到堆转储定位 一起,构成「网关—数据库—系统—内存」的线上排障四部曲。

一、先分清 502 和 504:它们不是一回事

症状像,根因完全不同。502 是「上游连上了但没给合法响应」(后端崩了、进程没了、响应头畸形);504 是「上游在规定时间内没回完」(后端太慢、超时太短、被阻塞)。不区分清楚,就会在错误的方向上瞎改。下面这张对照表建议存下来:

维度502 Bad Gateway504 Gateway Timeout
含义上游返回了无效/空响应上游在超时内没返回完整响应
常见诱因后端进程挂掉、端口没监听、响应头过大后端慢查询/慢接口、proxy_read_timeout 太短
第一看哪里error.log 的 connect()/recv() 失败error.log 的 upstream timed out
最快止血拉起后端 / 摘掉故障节点临时调大 proxy_read_timeout

一句话记忆:502 看「通不通」,504 看「快不快」。

二、第一步永远是看 error.log,不是猜

别凭感觉重启。Nginx 的 error.log 已经把根因写在脸上,关键是从日志里抓关键字:是 timed out 还是 connect() failed。下面两段是本次事故里真实出现过的信号:

# Nginx error.log 里最典型的两类信号
2026/08/20 10:14:03 [error] 1234#0: *88231 upstream timed out
  (110: Connection timed out) while reading response header from upstream,
  client: 10.0.0.9, server: api.fsdata.site,
  upstream: "http://10.0.1.20:8080/order", host: "api.fsdata.site"

2026/08/20 10:14:05 [error] 1234#0: *88240 connect() failed
  (99: Cannot assign requested address) while connecting to upstream,
  upstream: "http://10.0.1.20:8080/order" 

第一行是典型的 504:upstream timed out,说明 Nginx 连上了后端、但等响应超时;第二行是 502 前兆:connect() failed (Cannot assign requested address),说明本机已经没法再发起新连接——这通常是端口或连接池耗尽,而不是后端代码的问题。日志先看这一眼,方向就定了。

三、504 主因:proxy 超时配置没贴合业务

Nginx 反向代理默认 proxy_read_timeout 是 60s,但很多业务接口(报表导出、批量任务、上游再调第三方)本身就超过 60s。后端还在算,Nginx 已经把连接掐了,于是前端收到 504。这类问题在 Nginx 反向代理完整配置:负载均衡 + HTTPS + 缓存优化 的反向代理配置基础上,要针对慢路径单独放宽超时:

# 反向代理超时:把默认值从 60s 提到业务能接受的上限
location /api/ {
    proxy_pass http://backend;
    proxy_connect_timeout 5s;      # 建连超时
    proxy_read_timeout    30s;     # 读响应超时(504 主因)
    proxy_send_timeout    30s;
    proxy_http_version 1.1;
    proxy_set_header Connection ""; # 关键:关闭短连接
}

注意最后两行:proxy_http_version 1.1 配合 proxy_set_header Connection “” 关闭短连接,否则每个请求都新建 TCP,高并发下很快把本地端口耗尽(回到上面的 502 前兆)。超时不是越大越好,30s 足够覆盖正常慢接口,又能挡住真正卡死的请求。

怎么确认是「超时」而不是「后端直接挂了」?绕过 Nginx 直连上游压一下,用 curl 把各阶段耗时打出来,和 Nginx 看到的报错时间点对比:

# 直连后端,测量建连 / 首字节 / 总耗时
$ curl -o /dev/null -s -w "connect=%{time_connect} ttfb=%{time_starttransfer} total=%{time_total}\n" \
    http://10.0.1.20:8080/order
# 若 total 稳定在 70s,而 Nginx 在 60s 就报 504,根因就是 proxy_read_timeout 太短

直连耗时和 Nginx 报错时间点对得上,就能坐实是超时配置问题,而不是后端进程崩溃——后者会直接 502,且 error.log 里是 connect() failed 而不是 timed out。

四、502 主因:连接池 / 端口耗尽

当 502 伴随 connect() failed (Cannot assign requested address),问题已经从「后端慢」变成「本机连不出去」。根因多半是 upstream 没配 keepalive,或 keepalive 没生效,导致海量短连接堆积成 TIME_WAIT,把 ip_local_port_range 打满。修复分两处:

4.1 upstream 开启 keepalive

# 后端 upstream 连接池:keepalive 必须配合 http1.1 + 清空 Connection
upstream backend {
    server 10.0.1.20:8080;
    server 10.0.1.21:8080;
    keepalive 64;                  # 每个 worker 保活连接数
}

# 排查本机是否打满 TIME_WAIT / 端口耗尽
$ ss -ant | awk '{print $1}' | sort | uniq -c
$ cat /proc/sys/net/ipv4/ip_local_port_range
$ sysctl net.ipv4.tcp_tw_reuse   # 建议 =1

keepalive 64 表示每个 worker 保活 64 条长连接;它必须和前面的 proxy_http_version 1.1 + Connection 清空一起用才生效。改完用 ss -ant 观察 TIME_WAIT 数量应明显回落。

4.2 后端自身的连接数也要对齐

如果后端是 Java 应用,Tomcat/Undertow 的最大连接数、数据库 HikariCP 连接池上限都要和 Nginx 的并发匹配,否则上游自己先堵死,Nginx 照样 502。连接数这类系统级瓶颈,往往和 Java 内存泄漏排查:从监控告警到堆转储定位 的内存压力、文件描述符耗尽导致服务假死:一次 fd 泄漏排查实录 的 fd 耗尽相伴出现,排查时要一起看。

五、别忘了:502/504 可能只是表象

网关报错常常是「替罪羊」。后端为什么慢?大概率是里面的慢 SQL(参考 慢 SQL 拖垮生产库:一次 CPU 100% 排查实录 的 CPU 100% 排查)、Full GC 卡顿(参考 Java 内存泄漏排查:从监控告警到堆转储定位),或者下游依赖超时。所以 Nginx 层先把超时和连接池调对,只是止血;要根治必须进到后端去抓真正的慢点,否则把 proxy_read_timeout 调到 300s,只是把用户等待从「秒级报错」变成「分钟级转圈」。本次事故最终定位到的,正是订单接口里一条没走索引的联表查询,高峰期把 RT 从 200ms 拉到 80s——典型的 慢 SQL 拖垮生产库:一次 CPU 100% 排查实录 场景,网关只是第一个替它背锅的。

六、线上排查清单(照着过一遍)

顺序动作看什么
1看 Nginx error.log 关键字timed out → 504;connect() failed → 502/端口
2区分 502/504通不通 vs 快不快
3查本机连接ss -ant 看 TIME_WAIT / 端口耗尽
4查 upstream 配置keepalive 是否生效、超时是否合理
5进后端查真因慢 SQL / GC / fd,参考排障三部曲
6复压验证调完用压测确认 502/504 归零

小结

Nginx 502/504 排查的诀窍就一句:先读日志分 502/504,再分别治「连接池」和「超时」。502 多半是 upstream 没开 keepalive 导致端口耗尽,504 多半是 proxy_read_timeout 没贴合业务慢路径。这两处改对,大部分网关类故障当场止血;但要彻底根治,还得顺着网关进到后端,把慢 SQL 和内存问题一起解决——这正是一份好的线上排障实录该串起来的全链路视角。顺手把 Nginx 开启 Gzip 与浏览器缓存:让网站加载快一倍 的缓存与压缩配置也对齐,能让网关层更稳。

上一篇 技术方案评审实战:从设计文档到把关清单
下一篇 在线改大表实战:pt-osc 与 gh-ost 零停机