一次看似玄学的 502 排查:不是浏览器问题,而是 Nginx 老 Worker 没退干净
背景
那天同事在群里扔了一句话:
“官网在 Chrome 里有时候会 502,但开无痕又正常,这到底是前端问题还是服务器问题?”
一开始我也以为是缓存、扩展、或者某个奇怪的浏览器兼容问题。结果最后发现,是一个非常典型但不太直觉的线上问题:Nginx 新旧 worker 并存,旧 worker 还在吃流量,并且持有旧配置。
故障现象
- 业务域名:
https://www.***.com/home - 现象:
- 普通窗口偶发
502 Bad Gateway - 无痕窗口“看起来更正常”
Ctrl+Shift+R强刷后更容易出现 502
- 普通窗口偶发
这个特征非常迷惑人,因为它会让人优先怀疑浏览器侧问题。
第一轮排查:先看日志
先看访问日志和错误日志,确认是不是服务端确实在返 502。
tail -f /var/log/nginx/access.log
tail -f /var/log/nginx/error.log
很快确认了两件事:
502确实存在,不是前端展示问题。- 触发时段里,错误日志是标准的 upstream 连接失败:
connect() failed (111: Connection refused) while connecting to upstream
upstream: "http://127.0.0.1:4000/home"
server: www.***.com
也就是:Nginx 正在把请求转发到 127.0.0.1:4000,但这个端口拒绝连接。
第二轮排查:验证 4000 端口是否真的挂了
ss -lntp | grep :4000 || echo "4000 没有在监听"
curl -I http://127.0.0.1:4000/home
输出很直接:
4000 没有在监听curl: (7) Failed to connect ... Connection refused
到这里已经能解释 502 的直接原因了:上游服务不在。
但奇怪的是:我明明改过 Nginx 配置了
继续查当前生效配置:
nginx -T | grep -nE 'server_name www\.***\.com|proxy_pass|127\.0\.0\.1:4000'
结果更诡异:nginx -T 看到的配置里,站点代理已经不是 127.0.0.1:4000 了。
换句话说:磁盘配置是新的,但线上错误日志还在打旧 upstream。
真正根因:旧 worker 没退,仍持有旧内存配置
我去看 worker 进程:
ps -eo pid,ppid,lstart,cmd | grep 'nginx: worker process' | grep -v grep
看到了“关键证据”:
- 一部分 worker 是刚 reload 出来的新时间
- 另一部分 worker 是很多天前的老时间(一直没退出)
这就通了:
- 老配置时代,站点走过
127.0.0.1:4000(SSR 方案)。 - 后来 Node 服务(4000)被下掉(比如 pm2 删除)。
- Nginx 做过 reload,但老 worker 没完全退出。
- 新 worker 走新配置,老 worker 走旧配置。
- 请求随机落到不同 worker,所以出现随机
200/502。
为什么“无痕更正常、强刷更容易报错”?
这点其实是“现象”不是“根因”:
- 无痕会有不同的缓存/连接状态,有时降低了命中失败请求的概率。
- 强刷会绕过缓存,直接命中实时链路,更容易撞到坏 worker。
所以它看起来像浏览器问题,本质还是服务端链路不一致。
最终修复
先让老 worker 优雅退出:
kill -QUIT <old_worker_pid1> <old_worker_pid2> <old_worker_pid3>
如果还残留,再补 TERM。
确认只剩新 worker 后,回归验证:
for i in $(seq 1 30); do
curl -k -s -o /dev/null -w "%{http_code}\n" \
--resolve www.***.com:443:127.0.0.1 \
https://www.***.com/home
done | sort | uniq -c
结果:30 x 200,0 x 502。问题消失。
顺便回答一个常见误区:pm2 都删了,为什么还有 worker?
因为它们不是一类进程:
pm2管的是 Node 应用进程nginx worker是 Nginx 自己的进程
所以“pm2 删了 Node 服务”这件事,不会自动影响 Nginx worker 的生命周期。
如果 Nginx 老 worker 还活着,它仍可能按旧配置转发,继续把请求打到已下线端口。
这次事故的可复用 checklist
线上出现“偶发 502、可复现但不稳定”时,我现在会先跑这组:
# 1) 错误日志里看 upstream 指向谁
tail -f /etc/nginx/log/<site>_error.log | grep --line-buffered 'upstream\|connect() failed'
# 2) 目标上游端口是否监听
ss -lntp | grep :<port>
curl -I http://127.0.0.1:<port>/<path>
# 3) 当前配置与运行进程是否一致
nginx -T | grep -nE 'server_name|proxy_pass|<port>'
ps -eo pid,ppid,lstart,cmd | grep 'nginx: worker process' | grep -v grep
# 4) 强制本机回归测试
for i in $(seq 1 30); do
curl -k -s -o /dev/null -w "%{http_code}\n" \
--resolve <domain>:443:127.0.0.1 \
https://<domain>/<path>
done | sort | uniq -c
总结
这次故障最“坑”的地方在于:
你看到的是浏览器层面的随机报错,但根因其实是 Nginx worker 代际不一致。
reload只能加载新的Nginx配置,需要使用systemctl restart nginx来重启
一句话概括:
旧 worker + 旧 upstream + 已下线端口 = 随机 502。
如果你也遇到“无痕正常、强刷报错、线上随机 502”的场景,建议把“老 worker 残留”放进第一批排查项。