跳转至

Nginx 反向代理实战:502 / 504 三种根因决策树

线上环境出现 502 Bad Gateway,第一反应不是重启 Nginx。重启是最后一步,不是第一步。502 和 504 都是 Nginx 到上游(upstream)的链路问题,Nginx 本身几乎从不出错。排查顺序应该是:先问上游活着吗,再问上游慢不慢,再问 SSL 对不对,最后才动 Nginx。

5 分钟定位:三条命令先跑起来

排障的第一原则是先缩小范围。这三条命令 5 分钟内能跑完,能排除 80% 的 502 场景。

命令一:curl -v 直接问上游端口

绕过 Nginx,直接问上游进程。如果这条命令都失败,说明问题不在 Nginx 层。

# 假设上游是 127.0.0.1:8080,前端 Nginx 监听 443
curl -v --connect-timeout 5 http://127.0.0.1:8080/

你应该看到什么: - Connected to 127.0.0.1 → 上游进程活着,问题在别处 - Connection refused → 上游端口没起来,直接 systemctl status <app> 或 docker ps 查 - Connection timed out → 防火墙 / iptables 拦了,看 iptables -L

命令二:tail -f Nginx 错误日志

Nginx 的错误日志是唯一权威信源。access.log 只告诉你"发生了什么",error.log 才告诉你"为什么"。

tail -f /var/log/nginx/error.log | grep -E '502|upstream'

你应该看到什么(几种典型报错): - connect() failed (111: Connection refused) while connecting to upstream → 上游端口没开 - upstream prematurely closed connection while reading response header from upstream → 上游进程处理中崩溃 - upstream timed out (110: Connection timed out) → 上游太慢 - SSL_do_handshake() failed → 上游是 HTTPS 但证书 / 协议不匹配

命令三:ss -tanp 看连接状态

ss 是 netstat 的现代替代,性能更好。看 Nginx 与上游之间的连接处在哪个 TCP 状态。

ss -tanp | grep -E ':8080|:443'

你应该看到什么: - ESTAB → 连接已建立,正常 - SYN-SENT → Nginx 在等上游回 SYN-ACK,上游没响应 - CLOSE-WAIT → 上游主动关连接但 Nginx 没关,可能是 upstream keepalive 配置问题

跑完这三条命令,你就能定位到 502 的三大类根因之一。下面按根因分场景展开。

场景一:upstream 进程崩溃(占 502 场景 90%)

症状匹配:error.log 反复出现 upstream prematurely closed connection,curl 上游端口有时成功有时失败,上游进程日志里有 OOM、栈溢出、panic 之类的报错。

常见子原因:

  1. 上游进程内存不足被 OOM Killer 杀掉。这是 Node.js 和 Go 服务最常见的问题。
  2. 上游进程处理某个请求时 panic(Java NPE、Python unhandled exception)。
  3. 上游是长连接服务(gRPC / WebSocket),Nginx 默认超时时间短于业务需要,Nginx 先关了连接。

排查步骤:

# 1. 看 upstream 进程是否活着
systemctl status <your-app>
# 或 Docker 场景:
docker ps --filter name=<your-app>

# 2. 看进程是否被 OOM 杀
dmesg | grep -i 'oom\|killed' | tail -20

# 3. 看进程最近 100 行日志
journalctl -u <your-app> -n 100 --no-pager
# 或 Docker:
docker logs <container> --tail 100

修复:如果是 OOM,扩上游内存或加 swap;如果是 panic,改代码;如果是长连接超时,改 Nginx proxy_read_timeout。

验证:修复后 curl -v 上游端口 100 次不失败。

场景二:upstream 端口不通(占 7%)

症状匹配:curl -v 上游端口返回 Connection refused,ss -tanp 没有相关 ESTAB 连接。

常见子原因:

  1. 上游进程没起来。服务挂了或初始化卡住。
  2. 上游监听地址配错。应用监听了 0.0.0.0:8080 但 Nginx 试图连 127.0.0.1:8080,或者反过来应用只监听 127.0.0.1 而 Nginx 从别的机器访问。
  3. iptables / 安全组拦截。

排查步骤:

# 1. 检查监听端口
ss -tlnp | grep ':8080'
# 如果没输出,说明上游没在 8080 监听

# 2. 从 Nginx 机器访问上游端口(跨机场景)
ssh nginx-server 'curl -v http://upstream-server:8080/'

# 3. 检查防火墙
iptables -L INPUT -n | grep 8080

修复:按子原因对应处理——拉起上游进程 / 修正监听地址 / 放行安全组规则。

验证:ss -tlnp | grep :8080 有输出,curl -v 成功。

场景三:upstream 是 HTTPS 但证书或协议不匹配(占 3%)

症状匹配:curl -v 上游端口报 TLS handshake 错误,Nginx error.log 报 SSL_do_handshake() failed。

常见子原因:

  1. Nginx proxy_pass 用 http 但 upstream 只支持 https。
  2. upstream 证书是自签的,Nginx 拒绝验证。
  3. TLS 版本不匹配(Nginx 默认 TLS 1.2+,但 upstream 只支持 TLS 1.0)。

排查步骤:

# 1. 直接测上游 TLS
openssl s_client -connect 127.0.0.1:8443 -servername <your-host>

# 2. curl 用 https 打上游
curl -vk https://127.0.0.1:8443/

# 3. 看 Nginx 配置里 proxy_pass 是不是 https://
grep 'proxy_pass' /etc/nginx/nginx.conf /etc/nginx/conf.d/*.conf

修复:改 Nginx proxy_pass https://upstream,若 upstream 是自签证书,加 proxy_ssl_verify off;(不推荐生产使用)或把自签 CA 加到系统信任。

验证:openssl s_client 能完成握手,Nginx 无 SSL_do_handshake 报错。

场景四:keepalive 导致间歇性 502

症状匹配:502 是间歇性的,不是 100% 失败,且失败时 error.log 报 recv() failed (104: Connection reset by peer)。

根因:Nginx 与 upstream 之间用了 keepalive 复用连接,但上游主动关连接(如 Nginx 与 Node.js 之间默认的 idle timeout 不一致),Nginx 拿到的连接已经死了。

修复:

upstream backend {
    server 127.0.0.1:8080;
    keepalive 32;                    # Nginx 侧空闲连接池
    keepalive_timeout 65s;           # 关键:比上游 idle timeout 短
    keepalive_requests 1000;
}

location / {
    proxy_http_version 1.1;          # keepalive 必须 HTTP/1.1
    proxy_set_header Connection "";  # 必须清空 Connection 头
    proxy_pass http://backend;
}

关键点:proxy_http_version 1.1 和 Connection "" 两行必须同时出现,缺一都不生效。Nginx 默认 HTTP/1.0 不支持 keepalive。

场景五:upstream 是 SSL 证书链不完整

症状匹配:curl 上游端口报 unable to verify the first certificate,或 SSL certificate problem: unable to get local issuer certificate。

修复:

# 从上游机器导出完整证书链
openssl s_client -connect upstream:443 -showcerts </dev/null 2>/dev/null | awk '/BEGIN CERT/,/END CERT/' > /path/to/fullchain.pem
location / {
    proxy_pass https://upstream;
    proxy_ssl_trusted_certificate /path/to/fullchain.pem;
    proxy_ssl_verify on;
}

502 vs 504 vs 499:三种网关错误一次说清

状态码 Nginx 视角 上游视角 常见原因
502 Bad Gateway Nginx 收到无效响应 上游崩溃或拒绝连接 上游进程挂、端口不通、协议不匹配
504 Gateway Timeout Nginx 等响应超时 上游活着但太慢 上游处理时间长、DB 慢查询、proxy_read_timeout 太短
499 Client Closed Request Nginx 正常响应前 客户端已断 浏览器 timeout、用户刷新、上游响应慢导致客户端放弃

排查顺序:先分是 502 还是 504,再往下钻。502 走上文五个场景;504 主要看 proxy_read_timeout 和上游性能。

location 匹配优先级:4 种模式别搞错

502 有时不是 502,是你请求打到了错误的 location 上。Nginx 的 location 匹配顺序:

  1. = 精确匹配(最高优先级):location = /exact { ... }
  2. ^~ 前缀匹配且跳过正则:location ^~ /static/ { ... }
  3. ~ 正则匹配(区分大小写):location ~ \.php$ { ... }
  4. / 普通前缀匹配(最低优先级):location / { ... }

排查命令:

# Nginx 会输出合并后的完整配置
nginx -T | grep -A 5 'location'

Cheat Sheet:20 条命令 + 5 个配置片段

20 条常用命令速查

# 命令 用途
1 nginx -t 测试 Nginx 配置语法
2 nginx -s reload 热加载配置
3 nginx -T 输出合并后的完整配置
4 tail -f /var/log/nginx/error.log 实时看错误日志
5 curl -v http://127.0.0.1:8080/ 绕过 Nginx 直接测上游
6 curl -vk https://127.0.0.1:8443/ 测上游 TLS
7 ss -tanp | grep 8080 看 8080 端口连接状态
8 ss -tlnp | grep :8080 看谁在监听 8080
9 netstat -anp | grep 8080 老命令,功能同 ss
10 tcpdump -i any port 8080 -c 20 抓 8080 前 20 个包
11 tcpdump -i any 'tcp[tcpflags] & tcp-rst != 0' 抓 TCP RST
12 openssl s_client -connect host:443 测 TLS 握手
13 openssl s_client -connect host:443 -showcerts 拿完整证书链
14 dig +short <upstream-host> 解析上游域名
15 nslookup <upstream-host> 备选 DNS 查询
16 strace -p <pid> -e trace=network 追进程网络调用
17 systemctl status nginx 看 Nginx 服务状态
18 journalctl -u nginx -n 50 看 Nginx 最近 50 条日志
19 docker logs <container> 看容器日志
20 dmesg | grep -i oom 查 OOM Killer

5 个高频配置片段

1. 三超时(最容易漏配)

location / {
    proxy_connect_timeout 10s;
    proxy_read_timeout 60s;
    proxy_send_timeout 60s;
    proxy_pass http://backend;
}

2. upstream keepalive(HTTP/1.1 + Connection 头必须同时配)

upstream backend {
    server 127.0.0.1:8080;
    keepalive 32;
    keepalive_timeout 65s;
}
location / {
    proxy_http_version 1.1;
    proxy_set_header Connection "";
    proxy_pass http://backend;
}

3. 缓存

proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=mycache:10m max_size=1g inactive=60m;
location / {
    proxy_cache mycache;
    proxy_cache_valid 200 10m;
    proxy_cache_key "$scheme$request_method$host$request_uri";
    proxy_pass http://backend;
}

4. WebSocket 反代(HA / 实时应用必备)

location /ws {
    proxy_pass http://backend;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $host;
}

5. 日志记录 502(排障后加,方便未来回看)

log_format proxy_debug '$remote_addr [$time_local] "$request" $status $body_bytes_sent '
                       '"$upstream_addr" $upstream_status $upstream_response_time';
access_log /var/log/nginx/access.log proxy_debug;

如果都排不出:下一步去哪里

上面五条场景覆盖 95% 的 502 案例。如果都排完还是 502,问题不在应用层,在网络层。这时候该抓包了。

推荐路径:

  1. 抓包分析:用 Wireshark 或 tcpdump 抓 Nginx 与 upstream 之间的包,看是否有 RST、SYN 半连接、异常重传。详见本站 Wireshark 抓包入门。
  2. TCP 层排查:TIME_WAIT、CLOSE_WAIT 堆积、SYN 队列满等 TCP 状态异常也会导致 502。详见本站 TCP 排障。
  3. 官方文档:
  4. Nginx proxy_pass 官方文档
  5. Nginx troubleshooting 官方指南
  6. ss 命令 man page

延伸阅读


本文由 ops.jiunotes.top 独立撰写。所有命令、配置片段、场景分析均为作者实操经验总结,未搬运任何第三方博客内容。CC BY-NC-SA 4.0。