Nginx出现502 Bad Gateway怎么办?PHP-FPM与上游服务排查

早上九点,业务群里连续弹出截图:页面白屏,中间一行 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 upstreamupstream 池里所有节点都被判定失败检查 max_fails / fail_timeout 与健康状态

1.2 怎么区分 502 和 504

这两个状态码经常被混为一谈,但它们的成因和处置路径完全不同,误判会导致你往错误的方向排查。核心区别在于连接有没有建立成功:

对比项502 Bad Gateway504 Gateway Timeout
本质连接没建立,或连接被上游提前关闭连接建成、请求已发出,但等待响应超时
日志关键字connect() failed / prematurely closed / Connection resetupstream 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 socketTCP 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_sparedynamic 模式下进程数量弹性不足时,突发流量来不及创建进程瞬时并发上来就 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.backlogaccept 队列满后新连接被内核丢弃并发高时随机 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 分钟内能定位:

  1. 确认现象范围:全部接口挂还是单个接口挂?刷新是否偶发恢复?静态资源是否正常?(静态正常、动态挂 → 问题在 FastCGI/后端)
  2. 看错误日志:tail -n 100 /var/log/nginx/error.log,对照第 1.1 节表格判断属于哪一类。
  3. 确认上游存活:systemctl status php8.1-fpm 或 systemctl status your-app。
  4. 确认监听一致:ss -lntp | grep 9000 与 grep -rn fastcgi_pass /etc/nginx/ 对比;socket 方式再查文件是否存在与权限。
  5. 本机直连后端:cgi-fcgi -bind -connect /run/php/php8.1-fpm.sock 或 curl -I http://127.0.0.1:9000,排除 Nginx 干扰。
  6. 看进程池是否打满:curl 127.0.0.1/fpm-status,关注 listen queue 与 max children reached。
  7. 查资源瓶颈:free -m、dmesg -T | grep -i oom、df -h、df -i、ulimit -n。
  8. 查依赖:数据库是否可连(mysqladmin ping)、Redis 是否超时——后端等依赖超时会被 Nginx 判为上游失败。
  9. 改完验证: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、错误日志分析。