故障排查 · 2026 年 11 月 11 日

Nginx 502 排查手册:从 upstream 到文件描述符

502 不是一种错误,而是一类。本文按上游连接、worker 容量、文件描述符三条线索,把 error.log 里的关键字翻译成可执行的排查步骤和修复参数。

502 是 Nginx 说得最含糊的一句话。它只知道上游没有给出合法响应,至于是连不上、连上又被关掉,还是自己已经没有连接槽可用,全靠 error.log 里那一行。遇到 502,第一件事不是重启,而是把那行日志读成一句人话。

下面这套流程按上游连接、上游进程、worker 容量、文件描述符四层展开。顺序不能颠倒:先证伪最外层,再往里收。每一层都有对应的命令和日志关键字,跑完基本能收敛到一个具体参数,而不是靠猜。

现象:502 至少有四种写法#

同样返回 502,错误日志完全不同。先把它们归类,后面每一步才有方向。

日志关键字 含义 优先怀疑
connect() failed (111: Connection refused) 到上游的 TCP 连接被拒 进程没监听、端口写错、只监听回环地址
upstream prematurely closed connection 上游接了连接立刻关掉 上游超时自杀、崩溃重启、协议不匹配
no live upstreams 健康检查后无可用节点 节点全被标记 down、max_fails 被打满
worker_connections are not enough worker 的连接槽耗尽 并发过高、长连接不回收、fd 上限太低

第四类最容易被误判。它看起来像纯容量问题,实际常常是文件描述符先被打满:Nginx 既无法 accept 新连接,也无法向上游发起连接,于是两类错误交替出现,越重启越频繁。

顺带区分 502、504 与 499。502 是拿不到合法响应,504 是等太久超过 proxy_read_timeout,499 是客户端自己先断开、Nginx 记下的状态码。三者经常同时出现:上游变慢导致 504 增多,客户端等不及断开产生 499,连接被占满后新请求直接 502。看到这种组合,容量和超时才是主线,不必从 fd 查起。

定位:三条命令把问题归层#

先确认上游到底有没有在监听,再看 Nginx 自己的账本。在 Nginx 所在机器上执行:

bash
# 上游是否在监听,本机能否直连
ss -lntp | grep ':8080'
curl -sv --max-time 3 http://127.0.0.1:8080/health 2>&1 | tail -20

# Nginx 的连接与 fd 账本
nginx -T | grep -E 'worker_connections|worker_rlimit_nofile|keepalive'
ss -s
cat /proc/$(cat /run/nginx.pid)/limits | grep -i 'open files'
ls /proc/$(cat /run/nginx.pid)/fd | wc -l

本机 curl 通、经 Nginx 不通,问题在 Nginx 到上游这一段;本机 curl 也不通,先修上游,别在 Nginx 上折腾。如果是容器环境,注意 127.0.0.1 指的是 Nginx 自己所在的网络命名空间,不是宿主机。

ss -s 第一行的 TCP: ... estab ..., timewait ... 很有信息量。timewait 数量持续上万,说明短连接太多,keepalive 大概率没生效;estab 接近 worker_connections × worker 数,说明连接槽快满了。这两个数字比 5xx 比例更早预警。

worker_connections 乘 worker 进程数只是理论上限,真正卡死你的往往是 worker_rlimit_nofile 和系统 ulimit -n。fd 使用率超过 80% 时,accept 与 connect 会同时失败,日志里两种错误交替出现。

还有一点常被漏算:proxy_next_upstream 默认在 error、timeout 时换节点重试。一次客户端请求可能占用两到三个上游连接,容量估算要按倍数放大。并发高时,把 proxy_next_upstream_tries 设成 2 比默认的无限重试安全得多。

要在测试环境复现「连上就断」,可以用一个收完连接立刻关闭的假上游:

bash
# 终端 A:监听但不回任何数据,收到就断
while true; do nc -l -p 8080 -q 0 < /dev/null; done
# 终端 B:反复请求,观察 error.log 的签名
for i in $(seq 20); do curl -s -o /dev/null -w '%{http_code} ' http://127.0.0.1/; done

日志里出现 upstream prematurely closed connection,就说明问题在这一层,与 fd、容量无关。

根因:三个高频组合#

上游只监听回环地址。 容器化之后 upstream 写成 127.0.0.1:8080,应用却只监听容器 IP,或者反过来。特征是稳定复现的 502,换台机器就正常,和并发无关。ss -lntp 的输出能直接看到监听地址,0.0.0.0:8080 与 127.0.0.1:8080 是两回事。

keepalive 写了但没生效。 upstream 块里加了 keepalive 32,却漏了 proxy_http_version 1.1 和清空 Connection 头。每个请求仍然新建连接,TIME_WAIT 堆积,本地端口耗尽后 connect 直接失败。验证方法是在 Nginx 上 curl -v 请求两次,看第二次是否出现 Re-using existing connection;没有这行,就说明连接没复用。

fd 只增不减。 发版、日志切割、热加载后老 worker 没退出,或某模块打开文件不关闭。ls -l /proc/<pid>/fd | grep deleted 出现大量已删除仍被持有的文件,就是典型信号。

还有一种情况是「时好时坏」:上游的 accept 队列(backlog)太小,突发连接被内核直接丢弃,表现像随机丢包。ss -lnt 的 Send-Q 列就是 backlog,配合 netstat -s | grep -i listen 的溢出计数可以验证。

另有一种误判:所有节点因为连续失败被 max_fails 打满,健康检查窗口内 Nginx 直接返回 no live upstreams,而上游其实早已恢复。这时先看 fail_timeout,再决定要不要临时摘除标记。max_fails=0 会关闭被动健康检查,适合上游只有单点、否则容易误伤。

修复:按顺序改,别一次动五个参数#

配置改动建议自上而下:先抬 fd 上限,再抬连接数,最后才动超时和重试。一次改五个参数,出了问题说不清是哪一个起的作用。

nginx
# nginx.conf 全局段
worker_rlimit_nofile 65535;

events {
    worker_connections 16384;
    multi_accept on;
}

http {
    upstream app {
        server 10.0.0.11:8080 max_fails=3 fail_timeout=10s;
        server 10.0.0.12:8080 max_fails=3 fail_timeout=10s;
        keepalive 64;            # 空闲长连接池大小
        keepalive_timeout 60s;
    }

    server {
        location / {
            proxy_http_version 1.1;          # keepalive 的前提
            proxy_set_header Connection "";   # 清掉 close
            proxy_connect_timeout 2s;         # 快速失败,别让请求排队
            proxy_read_timeout 30s;
            proxy_next_upstream error timeout http_502;
            proxy_next_upstream_tries 2;      # 限制重试放大
        }
    }
}

一个常被忽略的关系:每个上游连接也要占一个 fd,Nginx 文档建议 worker_rlimit_nofile 至少设成 worker_connections 的两倍,否则配置里的连接数只是纸面数字。

proxy_connect_timeout 从默认 60 秒降到 2 秒是关键一步。上游半死不活时,长超时会把 worker 连接长期占住,健康节点也会被拖成 502。

系统层同步放开,否则 Nginx 配置再大也会被内核挡住:

bash
# systemd unit 里加 LimitNOFILE=65535,或改 /etc/security/limits.conf
sysctl -w net.ipv4.ip_local_port_range="10240 65000"

改完 nginx -t && nginx -s reload,并确认旧 worker 在 worker_shutdown_timeout 内退出,而不是挂着继续占 fd。

防复发:让 502 可归因#

  • 对 no live upstreams 和 worker_connections are not enough 单独告警,它们比 5xx 比例更早暴露容量问题。
  • 采集 worker 的 fd 使用率,超过 70% 预警,而不是等 502 发生。
  • 监控 ss -s 里的 TIME_WAIT 数量,持续偏高就回头查 keepalive。
  • 健康检查探真实业务路径,别只探 /,否则上游依赖挂了仍然算健康。
  • 发版后检查 ps -ef | grep 'nginx: worker',确认旧进程全部回收。
  • 把 $upstream_addr、$upstream_status、$upstream_response_time 写进访问日志,事后能直接定位到节点。

结论#

排查顺序应该是:读日志归类,验证上游监听,核对 worker 与 fd 容量,最后检查 keepalive 与重试放大。四步之内基本能收敛到一个具体参数,而不是靠重启碰运气。

可以直接落地的动作有四条:把 proxy_connect_timeout 降到秒级;配 keepalive 时同时配好 HTTP/1.1 与 Connection 头;把 fd 使用率纳入监控;把 upstream 三元组写进访问日志。

还要记住 502、504、499 是一组信号:同时上涨说明堵在容量或超时上,盯单一指标容易被误导。

一句话收尾:重启能缓解的 502,通常是容量或泄漏问题,重启只是把计时器归零。

完

#Nginx#排障#Linux
回到顶部 ↑