摘要
按 error.log 关键字把 502 分为三类分支,逐一给出九种原因(进程未启动、监听地址不符、磁盘满、socket 权限、worker 打满、超时过短、keepalive 冲突、OOM、连接数耗尽)的定位命令与修复方式,附六步检查顺序与 logrotate 模板。
前提
nginx 1.18+,上游以 php-fpm 为例,路径按实际发行版调整。
1. 分类表
|----------------------------------------------|-------|------------|
| error.log 关键字 | 分支 | 优先排查 |
| connect() failed (111: Connection refused) | 上游不可达 | 存活、监听地址、磁盘 |
| upstream timed out | 上游无响应 | 性能、超时配置 |
| no live upstreams / 110: timed out | 上游全挂 | OOM、崩溃、网络 |
2. 定位命令(按分支)
bash
# 通用第一步
tail -100 /var/log/nginx/error.log
df -h
free -m
# 分支一
systemctl status php8.1-fpm --no-pager
ss -lntp | grep -E "9000|php"
grep -E "^listen" /etc/php/8.1/fpm/pool.d/www.conf
grep fastcgi_pass /etc/nginx/sites-enabled/*
# 分支二
ps --no-headers -o comm -C php-fpm8.1 | wc -l
grep -E "pm.max_children|pm =" /etc/php/8.1/fpm/pool.d/www.conf
# 分支三
dmesg -T | grep -i -E "killed process|out of memory" | tail -5
grep -E "worker_processes|worker_connections" /etc/nginx/nginx.conf
ulimit -n
3. 九种原因速查
|---|--------------|-----------------------|------------------------------|
| # | 原因 | 特征 | 修复 |
| 1 | 上游未启动 | refused + 服务 failed | 看 journal,修配置后启动 |
| 2 | 监听地址不符 | refused,socket/TCP 混用 | 统一 fastcgi_pass 与 listen |
| 3 | 磁盘满 | refused,df 100% | 清日志 + logrotate |
| 4 | socket 权限 | refused,sock 属主错 | listen.owner/group/mode |
| 5 | worker 打满 | timed out,进程数=上限 | 查慢查询,慎加 worker |
| 6 | 超时过短 | 仅特定接口 502 | fastcgi_read_timeout |
| 7 | keepalive 冲突 | 间歇性,重启即好 | 对齐 keepalive 或关闭 |
| 8 | OOM | 进程消失,dmesg 有记录 | 减 worker / 加 swap |
| 9 | 连接数耗尽 | worker_connections 提示 | 调高并核对 ulimit |
4. logrotate 补配
bash
cat > /etc/logrotate.d/nginx-custom <<'CONF'
/var/log/nginx/*.log {
daily
rotate 14
compress
missingok
notifempty
sharedscripts
postrotate
systemctl reload nginx > /dev/null 2>&1 || true
endscript
}
CONF
logrotate -d /etc/logrotate.d/nginx-custom # 演练,不实际执行
5. 六步检查顺序
tail -100 error.log分类df -h- 上游服务状态
- 监听地址与端口
- 内存与 OOM 记录
- 最后才是性能与超时参数
原则:先排除不可逆故障(磁盘、OOM),再做参数调优。
6. 监控:装 agent 还是走 SSH
事前告警比事后排查省时间。两条路:
- 装采集 agent(node_exporter 等):指标全、可长期留存,代价是常驻进程与新增端口
- 走 SSH 读取 :不装任何东西、不新增端口,直接读
/proc、日志与进程状态,界面在本地渲染
生产环境不允许装东西时只能走第二条。我用的是极目Linux桌面(英文名 GMSSH),域名 gm.cn------它的限制也说清楚:没有长期时序存储,做趋势分析仍需 TSDB,它解决的是「临时接管一台机器时快速看清状况」。
选型标准仍是那一条:是否在服务器上新增常驻服务与对外端口。
7. FAQ
Q1:fastcgi_read_timeout 调大就能解决超时吗?
只能掩盖。超时的根因是接口慢(慢查询、外部 API、锁等待),调大超时会让请求堆积得更久,最终把 worker 占满,变成另一种 502。调参前先看 php-fpm 的 slowlog。
Q2:怎么确认是 OOM 而不是进程自己崩的?
bash
dmesg -T | grep -i "killed process"
journalctl -k | grep -i oom
有 Out of memory: Killed process 就是 OOM。进程自己崩会留 PHP Fatal error 或 core dump,journal 里能看到。
Q3:pm.max_children 怎么算?
按「可用内存 ÷ 单进程常驻内存」估。ps -o rss= -C php-fpm8.1 | awk '{s+=$1} END {print s/NR/1024" MB"}' 拿到均值,再用 free -m 的 available 留 30% 给系统,剩下除一下。
Q4:为什么 502 只出现在高峰期?
典型的 worker 打满或连接数耗尽。高峰期看两处:ss -s 的连接总数、php-fpm 进程数是否贴住 pm.max_children。两者之一贴住就是根因。
Q5:改了 php-fpm 配置必须重启吗?
reload 就够(systemctl reload php8.1-fpm),平滑加载不影响在用请求。但 pm.max_children 这类进程管理参数,reload 后会按新值逐步调整,观察几分钟再看。
Q6:Keepalive 导致的间歇 502 怎么确认?
临时在 upstream 块里去掉 keepalive 并 reload,观察一天。如果 502 消失,就是连接复用到已关闭连接的问题,再回来对齐 keepalive_timeout 与上游的 pm.max_requests。
Q7:容器里的上游怎么排查?
多一层:docker ps 看容器是否在跑、docker port 看端口映射是否对、docker logs --tail 50 看应用日志。宿主机上的 nginx 连容器要么走容器 IP,要么走宿主机映射端口,别混。
Q8:有没有一键诊断脚本?
不建议。一键脚本会把「先分类」这一步跳过,输出的信息量反而更难判断。六条命令按顺序敲,比一个 200 行的脚本更快定位------尤其在你还不熟悉这台机器的时候。