早上九点,业务群里连续弹出截图:页面白屏,中间一行 502 Bad Gateway。你去服务器上敲 systemctl status nginx 显示 running,top 一看负载也不高,但页面就是打不开。刷新几次偶尔又能通——这种“时好时坏”的 502,几乎总是出在 Nginx 与上游(upstream)之间的那一段,而不是 Nginx 自己。
502 的本质定义是:Nginx 作为网关或代理,从上游服务器收到了一个无效响应,或者根本没收到响应。注意它和 504 的区别——504 是“等太久超时”,502 是“拿到的东西不对或连接没建立”。本文按分层排查链路展开:上游进程存活 → 监听地址与 socket 权限 → 请求头与缓冲区 → 后端自身崩溃 → 资源瓶颈,每一层都给出可直接执行的命令和日志关键字段。
一、先看日志:30 秒定位方向
不要靠猜。502 的排查起点永远是 Nginx 错误日志,Nginx 会把上游失败的原因原样记录下来。
# 实时跟踪错误日志
tail -n 200 /var/log/nginx/error.log
tail -f /var/log/nginx/error.log
# 统计最近一小时 502 出现的次数与上游地址
grep 'upstream prematurely closed connection' /var/log/nginx/error.log | tail -20
grep 'connect() failed' /var/log/nginx/error.log | awk '{print $NF}' | sort | uniq -c | sort -rn | head
1.1 错误日志关键字段速查
| 日志关键字 | 含义 | 下一步动作 |
|---|---|---|
| connect() failed (111: Connection refused) while connecting to upstream | 上游端口没人监听,进程没起或监听地址不对 | 检查 PHP-FPM / 后端进程状态与端口 |
| connect() failed (113: No route to host) | 网络不通,多为防火墙/安全组/跨机路由问题 | 检查 iptables、云安全组、SELinux |
| connect() to unix:/run/php/php8.1-fpm.sock failed (2: No such file or directory) | socket 文件不存在 | 检查 FPM 是否启动、listen 路径是否一致 |
| connect() to unix:/run/php/php8.1-fpm.sock failed (13: Permission denied) | socket 权限不足,Nginx 用户无权访问 | 检查 listen.owner / listen.mode 与 nginx 运行用户 |
| upstream prematurely closed connection while reading response header from upstream | 上游处理过程中自己断了,多为 PHP 致命错误、进程被 OOM kill、段错误 | 查 PHP-FPM 慢日志与 dmesg |
| upstream sent too big header while reading response header from upstream | 响应头超过缓冲区,常见于巨型 Cookie 或大量 Set-Cookie | 调大 proxy_buffer_size / fastcgi_buffer_size |
| recv() failed (104: Connection reset by peer) while reading response header from upstream | 上游进程崩溃或被重启 | 查 FPM 日志、request_terminate_timeout 是否过短 |
| no live upstreams while connecting to upstream | upstream 池里所有节点都被判定失败 | 检查 max_fails / fail_timeout 与健康状态 |
1.2 怎么区分 502 和 504
这两个状态码经常被混为一谈,但它们的成因和处置路径完全不同,误判会导致你往错误的方向排查。核心区别在于连接有没有建立成功:
| 对比项 | 502 Bad Gateway | 504 Gateway Timeout |
|---|---|---|
| 本质 | 连接没建立,或连接被上游提前关闭 | 连接建成、请求已发出,但等待响应超时 |
| 日志关键字 | connect() failed / prematurely closed / Connection reset | upstream timed out (110: Connection timed out) |
| 后端状态 | 多半已崩溃、未启动、或执行到一半被杀 | 还在跑,只是太慢 |
| 是否与耗时相关 | 与快慢无关,秒错 | 必然要等到超时阈值才出现 |
| 排查重点 | 进程存活、监听地址、socket 权限、缓冲区 | SQL 慢查询、外部 API、超时参数链路 |
| 复现特征 | 刷新偶尔成功,无固定时间规律 | 每次都要卡同样的秒数才失败 |
快速判读口诀:一刷就报错多半是 502,卡几十秒才报错多半是 504;看一眼 error.log 里是 connect() failed 还是 upstream timed out,方向立刻清晰。
二、第一层:上游进程到底活着吗
502 最常见的原因是上游进程压根没起来,或者起来后又退出了。
# PHP-FPM 状态与最近日志 systemctl status php8.1-fpm journalctl -u php8.1-fpm -n 100 --no-pager tail -n 100 /var/log/php8.1-fpm.log # 端口 / socket 是否真的在监听 ss -lntp | grep -E '9000|php' ss -lxnp | grep php-fpm # 其他后端(Node / Go / Java)同理 systemctl status your-app ss -lntp | grep -E '3000|8080|9000'
如果进程在反复重启,重点看 journalctl 里的退出原因。常见的三类:
- 配置语法错误:改完
www.conf没校验,php-fpm -t可以提前发现。 - OOM Killer 杀进程:
dmesg | grep -i 'out of memory'能看到Killed process 1234 (php-fpm)。 - 磁盘写满:session、日志目录满导致无法启动,
df -h与df -i都要看(inode 耗尽同样致命)。
# 修改配置后先校验再重载 php-fpm8.1 -t systemctl reload php8.1-fpm # 是否发生 OOM dmesg -T | grep -i -E 'out of memory|killed process' # 磁盘与 inode df -h df -i
三、第二层:监听地址与 socket 权限
Nginx 与 PHP-FPM 之间有两种通信方式:TCP 端口(127.0.0.1:9000)和 Unix socket(/run/php/php8.1-fpm.sock)。两边配置必须一模一样,任何一侧写错就直接 502。
3.1 两侧对照检查
# FPM 侧:确认实际监听地址 grep -nE '^\s*(listen|listen.owner|listen.group|listen.mode|user|group)' /etc/php/8.1/fpm/pool.d/www.conf # Nginx 侧:确认 fastcgi_pass 指向同一地址 grep -rn 'fastcgi_pass' /etc/nginx/ | grep -v '#'
典型正确配置(socket 方式):
# /etc/php/8.1/fpm/pool.d/www.conf
[www]
user = www-data
group = www-data
listen = /run/php/php8.1-fpm.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660
# /etc/nginx/conf.d/site.conf
location ~ \.php$ {
fastcgi_pass unix:/run/php/php8.1-fpm.sock;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
典型正确配置(TCP 方式,跨机或容器场景常用):
# www.conf listen = 127.0.0.1:9000 listen.allowed_clients = 127.0.0.1 # nginx fastcgi_pass 127.0.0.1:9000;
3.2 两种方式怎么选
| 对比项 | Unix socket | TCP 127.0.0.1:9000 |
|---|---|---|
| 性能 | 略高,省去 TCP 协议栈开销 | 略低,但同机差异通常在 5% 以内 |
| 权限模型 | 依赖文件权限,容易出 13 Permission denied | 依赖端口与 listen.allowed_clients |
| 跨机 / 容器 | 不支持 | 支持,Docker、多机部署必须用它 |
| 排障便利 | 只能用 ss -lxnp 看 | ss -lntp、telnet、curl 都能验 |
| 典型故障 | socket 文件被清理、权限不对 | 端口被占用、allowed_clients 漏配 |
权限修复命令(最常见的 502 修复动作):
# 假设 Nginx 运行用户为 nginx(CentOS/RHEL 常见)
ps aux | grep -E 'nginx|php-fpm' | grep -v grep | awk '{print $1}' | sort -u
chown -R www-data:www-data /run/php/
chmod 660 /run/php/php8.1-fpm.sock
systemctl restart php8.1-fpm
systemctl reload nginx
另外别忘了 SELinux(CentOS/RHEL 默认开启)会拦住 socket 访问:getenforce 若为 Enforcing,先 setenforce 0 验证是否是它导致,确认后用 setsebool -P httpd_can_network_connect 1 或调整上下文,不要长期关掉 SELinux。
四、第三层:PHP-FPM 进程池被打满
进程活着但“没人干活”,同样会表现为间歇性 502 或 504。这是中小站点最容易被忽视的一层。
# 查看进程池配置 grep -nE 'pm|max_children|start_servers|min_spare|max_spare|request_terminate' /etc/php/8.1/fpm/pool.d/www.conf # 当前子进程数量 ps aux | grep 'php-fpm: pool www' | grep -v grep | wc -l # FPM 状态页(需先开启 pm.status_path = /fpm-status) curl http://127.0.0.1/fpm-status
4.1 关键参数含义与取值
| 参数 | 含义 | 默认值 / 建议 |
|---|---|---|
| pm | 进程管理方式:static / dynamic / ondemand | 内存稳定用 static;流量波动用 dynamic(默认) |
| pm.max_children | 最大子进程数,决定并发上限 | 默认 5。按“可用内存 / 单进程峰值内存”估算 |
| pm.start_servers | 启动时进程数 | 默认 2,建议设为 min_spare 左右 |
| pm.min_spare_servers / pm.max_spare_servers | 空闲进程上下限 | 默认 1 / 3,高峰站点要调大 |
| pm.max_requests | 处理多少个请求后重启子进程 | 默认 0(不重启),建议 500-1000 防内存泄漏 |
| request_terminate_timeout | 单请求硬超时,超时杀进程 | 默认 0(不限制),建议 30s-60s |
| listen.backlog | 监听队列长度 | 默认 511,高并发可调 2048 并同步调 net.core.somaxconn |
估算 pm.max_children 的实用方法:先测出单个 PHP 进程的实际占用。
# 统计每个 php-fpm 子进程的 RSS(KB)与平均内存
ps --no-headers -o rss,cmd -C php-fpm | awk '{s+=$1; n++} END {print "进程数:", n, "总内存KB:", s, "平均KB:", s/n}'
# 示例:可用内存 4G,单进程平均 40MB → max_children ≈ 4096*0.7/40 ≈ 70
如果 fpm-status 里 listen queue 持续大于 0、max children reached 计数在增长,说明进程池确实不够,需要调大 pm.max_children 或先去优化慢查询——盲目加大只会把内存吃光然后被 OOM。
4.2 进程池参数与 502 的对应关系
很多间歇性 502 查到最后都指向进程池配置。理解每个参数“失控时会产生什么现象”,比死记推荐值更重要:
| 参数 | 与 502 的对应关系 | 失控时的现象 | 处置 |
|---|---|---|---|
| pm.max_children | 决定并发上限。全部占满后新请求进不来,Nginx 侧连接无人应答 | fpm-status 中 max children reached 计数持续增长,业务高峰必然 502 | 按“可用内存×0.7÷单进程内存”重算;同时查慢 SQL,别盲目加大 |
| pm.start_servers / min_spare / max_spare | dynamic 模式下进程数量弹性不足时,突发流量来不及创建进程 | 瞬时并发上来就 502,几秒后自动恢复 | 提高 start_servers 与 min_spare_servers;或对稳定负载改用 static |
| pm.max_requests | 为 0 时子进程永不回收,内存泄漏会持续累积 | 站点运行几天后开始 502,重启 php-fpm 立刻恢复 | 设为 500-1000,让进程自动重启回收内存 |
| request_terminate_timeout | 超时会直接杀掉正在处理的子进程,连接被强拆 | 日志出现 prematurely closed connection;设置该值后超时的慢请求会表现为 502 而不是继续等待成 504 | 设为 30s-60s,并略大于 Nginx 的 fastcgi_read_timeout |
| listen.backlog | accept 队列满后新连接被内核丢弃 | 并发高时随机 502/连接重置,低峰完全正常 | 调 2048,同时把 net.core.somaxconn 调成一样大 |
| request_slowlog_timeout + slowlog | 不直接导致 502,但没有它就无法复盘 | 事后再也查不出哪个脚本出的问题 | 生产环境必须开启 |
推荐的一组可直接落地的池配置(按 8G 内存、单进程约 40MB 估算):
# /etc/php/8.1/fpm/pool.d/www.conf pm = dynamic pm.max_children = 100 # 8192 × 0.7 ÷ 40 ≈ 143,保守取 100 pm.start_servers = 20 pm.min_spare_servers = 10 pm.max_spare_servers = 30 pm.max_requests = 1000 # 让进程定期重启,避免内存缓慢泄漏 request_terminate_timeout = 30s request_slowlog_timeout = 3s slowlog = /var/log/php8.1-fpm.slow.log listen.backlog = 2048 # 与 sysctl 的 net.core.somaxconn 配套 # 配套的系统参数 sysctl -w net.core.somaxconn=2048 echo 'net.core.somaxconn = 2048' >> /etc/sysctl.conf
五、第四层:缓冲区、请求头与后端致命错误
这一类 502 的特征是:只有特定页面挂,其他页面正常。
5.1 响应头过大
# 反向代理场景 proxy_buffer_size 16k; proxy_buffers 8 16k; proxy_busy_buffers_size 32k; # FastCGI(PHP)场景 fastcgi_buffer_size 32k; fastcgi_buffers 8 32k; fastcgi_busy_buffers_size 64k;
触发条件是后端返回了超大的 Set-Cookie 或自定义头。根治办法是精简 Cookie,而不是无限调大缓冲区。
5.2 PHP 致命错误与慢日志
# 开启慢日志,定位究竟是哪个脚本拖死 slowlog = /var/log/php8.1-fpm.slow.log request_slowlog_timeout = 5s # PHP 错误日志 grep -nE 'error_log|log_errors|display_errors' /etc/php/8.1/fpm/php.ini tail -n 100 /var/log/php8.1-fpm.slow.log
注意 display_errors 在生产环境应为 Off,否则致命错误直接输出到页面,配合 HTTP 500/502 会泄露路径信息。
六、排查命令速查:按顺序复制执行
把常用的检查动作固化成一组命令,遇到 502 时从第一条执行到最后一条,绝大多数情况下在第三步之前就能定性。
6.1 第一组:看现象
# 1) 盯错误日志,确认 Nginx 给出的直接原因
tail -f /var/log/nginx/error.log
tail -n 200 /var/log/nginx/error.log | grep -iE 'upstream|connect\(\)|permission'
# 2) 看用户侧收到的状态码(对比 same 请求走 Nginx 与走直连的差异)
curl -I http://www.example.com/index.php
curl -s -o /dev/null -w "状态:%{http_code} 耗时:%{time_total}s 连接到后:%{time_connect}s\n" \
http://www.example.com/index.php
6.2 第二组:查上游
# 3) 上游进程状态(看 Active、最近退出原因、是否反复重启) systemctl status php8.1-fpm systemctl status nginx # 4) 监听是否在位:TCP 用 -t,Unix socket 用 -x ss -lntp | grep -E 'nginx|php|9000' ss -lxnp | grep php ls -l /run/php/php8.1-fpm.sock # 5) 绕过 Nginx 直连上游,确认后端自己能不能返回 # TCP 方式:telnet / nc 能连上即端口正常 nc -vz 127.0.0.1 9000 curl -I http://127.0.0.1:9000/ # socket 方式:用 cgi-fcgi 探活 cgi-fcgi -bind -connect /run/php/php8.1-fpm.sock # 6) 看 FPM 自己怎么说 journalctl -u php8.1-fpm -n 100 --no-pager tail -n 100 /var/log/php8.1-fpm.log curl http://127.0.0.1/fpm-status
6.3 第三组:查资源与依赖
# 7) 资源是否到底 free -m vmstat 1 5 df -h && df -i ulimit -n # 8) 是否被 OOM 杀过 dmesg -T | grep -i -E 'out of memory|killed process' # 9) 下游依赖是否可用(数据库挂了同样会把 PHP 请求拖成 502) mysqladmin ping redis-cli ping # 10) 改完配置务必:先校验,再平滑加载 nginx -t && systemctl reload nginx php-fpm8.1 -t && systemctl reload php8.1-fpm
七、标准排查流程清单
把上面的内容固化成一份可执行清单,遇到 502 按顺序走,通常 5 分钟内能定位:
- 确认现象范围:全部接口挂还是单个接口挂?刷新是否偶发恢复?静态资源是否正常?(静态正常、动态挂 → 问题在 FastCGI/后端)
- 看错误日志:
tail -n 100 /var/log/nginx/error.log,对照第 1.1 节表格判断属于哪一类。 - 确认上游存活:
systemctl status php8.1-fpm或systemctl status your-app。 - 确认监听一致:
ss -lntp | grep 9000与grep -rn fastcgi_pass /etc/nginx/对比;socket 方式再查文件是否存在与权限。 - 本机直连后端:
cgi-fcgi -bind -connect /run/php/php8.1-fpm.sock或curl -I http://127.0.0.1:9000,排除 Nginx 干扰。 - 看进程池是否打满:
curl 127.0.0.1/fpm-status,关注 listen queue 与 max children reached。 - 查资源瓶颈:
free -m、dmesg -T | grep -i oom、df -h、df -i、ulimit -n。 - 查依赖:数据库是否可连(
mysqladmin ping)、Redis 是否超时——后端等依赖超时会被 Nginx 判为上游失败。 - 改完验证:
nginx -t && systemctl reload nginx,再连刷 10 次确认稳定。
小结
502 Bad Gateway 不是一种“病”,而是 Nginx 在告诉你:我和上游之间那一段断了。排查的价值在于快速判断断在哪一段。请记住这个优先级:日志关键字 → 上游进程存活 → 监听地址与 socket 权限 → 进程池容量 → 缓冲区与后端致命错误 → 系统资源。其中“进程没起”和“socket 权限不对”两类占了日常 502 的绝大多数。
避坑建议:
- 改 FPM 配置前先
php-fpm -t,改 Nginx 前先nginx -t,这是零成本的习惯。 - 生产环境务必开启
slowlog与request_slowlog_timeout,否则 502 事后无法复盘。 pm.max_children不是越大越好,先算内存:可用内存 × 0.7 ÷ 单进程峰值。- 设置
request_terminate_timeout(如 30s)和pm.max_requests(如 500),防止单个请求或内存泄漏拖垮整池。 - 上游若在其他机器,务必确认云安全组与iptables 放行,
(113: No route to host)就是这个信号。 - 给
/fpm-status加访问限制,只允许 127.0.0.1,别把它暴露在公网。
关键词回顾:Nginx 502 Bad Gateway、PHP-FPM 排查、fastcgi_pass、upstream prematurely closed、pm.max_children、listen backlog、错误日志分析。
企业QQ咨询




