Nginx 反向代理实战:502 / 504 三种根因决策树
线上环境出现 502 Bad Gateway,第一反应不是重启 Nginx。重启是最后一步,不是第一步。502 和 504 都是 Nginx 到上游(upstream)的链路问题,Nginx 本身几乎从不出错。排查顺序应该是:先问上游活着吗,再问上游慢不慢,再问 SSL 对不对,最后才动 Nginx。
5 分钟定位:三条命令先跑起来
排障的第一原则是先缩小范围。这三条命令 5 分钟内能跑完,能排除 80% 的 502 场景。
命令一:curl -v 直接问上游端口
绕过 Nginx,直接问上游进程。如果这条命令都失败,说明问题不在 Nginx 层。
你应该看到什么:
- 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 才告诉你"为什么"。
你应该看到什么(几种典型报错):
- 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 状态。
你应该看到什么:
- ESTAB → 连接已建立,正常
- SYN-SENT → Nginx 在等上游回 SYN-ACK,上游没响应
- CLOSE-WAIT → 上游主动关连接但 Nginx 没关,可能是 upstream keepalive 配置问题
跑完这三条命令,你就能定位到 502 的三大类根因之一。下面按根因分场景展开。
场景一:upstream 进程崩溃(占 502 场景 90%)
症状匹配:error.log 反复出现 upstream prematurely closed connection,curl 上游端口有时成功有时失败,上游进程日志里有 OOM、栈溢出、panic 之类的报错。
常见子原因:
- 上游进程内存不足被 OOM Killer 杀掉。这是 Node.js 和 Go 服务最常见的问题。
- 上游进程处理某个请求时 panic(Java NPE、Python unhandled exception)。
- 上游是长连接服务(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 连接。
常见子原因:
- 上游进程没起来。服务挂了或初始化卡住。
- 上游监听地址配错。应用监听了
0.0.0.0:8080但 Nginx 试图连127.0.0.1:8080,或者反过来应用只监听127.0.0.1而 Nginx 从别的机器访问。 - 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。
常见子原因:
- Nginx
proxy_pass用 http 但 upstream 只支持 https。 - upstream 证书是自签的,Nginx 拒绝验证。
- 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 匹配顺序:
=精确匹配(最高优先级):location = /exact { ... }^~前缀匹配且跳过正则:location ^~ /static/ { ... }~正则匹配(区分大小写):location ~ \.php$ { ... }/普通前缀匹配(最低优先级):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,问题不在应用层,在网络层。这时候该抓包了。
推荐路径:
- 抓包分析:用 Wireshark 或
tcpdump抓 Nginx 与 upstream 之间的包,看是否有 RST、SYN 半连接、异常重传。详见本站 Wireshark 抓包入门。 - TCP 层排查:TIME_WAIT、CLOSE_WAIT 堆积、SYN 队列满等 TCP 状态异常也会导致 502。详见本站 TCP 排障。
- 官方文档:
- Nginx proxy_pass 官方文档
- Nginx troubleshooting 官方指南
- ss 命令 man page
延伸阅读
- TCP 排障:TIME_WAIT / RST / SYN 半连接 / 重传 — 502 的下游 TCP 层原因
- Wireshark 抓包入门:过滤器和常用字段速查 — 抓包工具的 30 条过滤器
- CDN 回源与缓存原理 — 502 有时其实是 CDN 侧问题
本文由 ops.jiunotes.top 独立撰写。所有命令、配置片段、场景分析均为作者实操经验总结,未搬运任何第三方博客内容。CC BY-NC-SA 4.0。