Nginx 502 Bad Gateway 完整排查思路和解决方案(PHP-FPM / 上游 / 超时三类原因全覆盖)

本文为个人运维实操笔记,记录一次线上 502 的完整排查过程,以及后续整理的通用排查清单。文中命令均在 Debian 12 + Nginx 1.24 + PHP-FPM 8.2 环境验证过,可直接复制使用。

一、测试环境说明

  • 服务器:1 核 1G Linux VPS(香港 CN2 节点,LuckVM),Debian 12
  • Web 环境:Nginx 1.24 + PHP-FPM 8.2 + MySQL 8.0
  • 站点程序:WordPress 6.x(宝塔面板部署)

说明一下我这台的背景:这是一个日访问量不大的个人博客,平时很稳,突然某个下午开始间歇性出现 502,刷新几次又能打开,属于典型的「偶发 502」。偶发比必现更难查,因为它考验的是排查思路,而不是运气。

二、先搞清楚 502 到底是谁报的

很多人一看到 502 就以为是 Nginx 挂了,其实绝大多数时候 Nginx 好好的,是它背后的「上游」出了问题。

502 Bad Gateway 的含义是:Nginx 作为反向代理,从「上游」(upstream)拿到了一个无效的响应,或者根本连不上上游。所以排查的第一步,永远是确定上游是谁。

2.1 判断上游类型

看站点的 Nginx 配置(一般在这几个位置):

# 宝塔面板的站点配置
ls /www/server/panel/vhost/nginx/

# 原生安装一般在
ls /etc/nginx/conf.d/
ls /etc/nginx/sites-enabled/

打开对应站点的 conf,找 location ~ \.php$ 或 proxy_pass:

location ~ \.php$ {
fastcgi_pass unix:/tmp/php-cgi-82.sock; # 上游是 PHP-FPM
...
}

location /api/ {
proxy_pass http://127.0.0.1:8080; # 上游是后端应用
...
}

  • fastcgi_pass 指向 PHP-FPM → 问题多半在 PHP-FPM
  • proxy_pass 指向后端服务 → 问题多半在 那个后端服务

我这台是 WordPress,上游就是 PHP-FPM,fastcgi_pass unix:/tmp/php-cgi-82.sock。

2.2 看错误日志先定位

不要一上来就瞎猜,先看日志。 两个地方都要看:

# Nginx 错误日志(宝塔在 /www/wwwlogs/)
tail -n 100 /www/wwwlogs/你的域名.error.log

# 或原生安装
tail -n 100 /var/log/nginx/error.log

# PHP-FPM 慢日志 / 错误日志
tail -n 100 /www/server/php/82/var/log/php-fpm.log

我当时的 error.log 里反复出现这一行:

2026/xx/xx 14:32:11 [error] 12345#0: *67890 connect() to unix:/tmp/php-cgi-82.sock failed (11: Resource temporarily unavailable) while connecting to upstream

关键信息是 Resource temporarily unavailable,这是最典型的 PHP-FPM 连接数被占满的报错,对应 PHP-FPM 的 listen.backlog 溢出或 pm.max_children 不够用。

到这里,方向就明确了:不是 Nginx 的锅,是 PHP-FPM 顶不住并发。

三、按错误信息对号入座

我把常见的 502 报错和对应原因整理成一张对照表,方便直接定位:

error.log 关键信息 含义 大概率原因
connect() to ... failed (11: Resource temporarily unavailable) 连接上游失败,资源暂时不可用 PHP-FPM 进程数满了 / backlog 溢出
connect() to ... failed (2: No such file or directory) 找不到 socket 文件 PHP-FPM 没启动,或 socket 路径配置不一致
connect() to ... failed (111: Connection refused) 拒绝连接 上游服务挂了,或没在监听该端口
upstream timed out (110: Connection timed out) 上游响应超时 后端处理太慢(数据库慢查询、死循环等)
recv() failed (104: Connection reset by peer) 被上游重置 PHP 进程崩溃 / 被 OOM Killer 杀掉
no live upstreams 没有可用上游 所有后端节点都挂了

有了这张表,配合日志里的关键词,基本能锁定大方向。

四、PHP-FPM 类 502 的排查与解决

回到我的案例,报错是 Resource temporarily unavailable,按下面几步走。

4.1 先看 PHP-FPM 进程状态

# 看 php-fpm 是否在跑
systemctl status php-fpm-82 # 宝塔的写法
# 或
systemctl status php8.2-fpm

# 看当前有多少个 php-fpm 子进程
ps -ef | grep php-fpm | grep -v grep | wc -l

# 更直观地看进程池状态
ps -ef | grep 'php-fpm: pool'

我当时用 ps 一数,子进程数量正好卡在 10 个,也就是 pm.max_children = 10 被占满了——并发一上来,没有空闲 worker 接活,新连接就进不来,直接 502。

4.2 看 PHP-FPM 配置

宝塔的配置在 /www/server/php/82/etc/php-fpm.d/www.conf,原生一般在 /etc/php/8.2/fpm/pool.d/www.conf:

grep -E 'pm\.|listen\.' /www/server/php/82/etc/php-fpm.d/www.conf

关注这几个参数:

pm = dynamic
pm.max_children = 10 # 最关键:最大子进程数
pm.start_servers = 2
pm.min_spare_servers = 2
pm.max_spare_servers = 5
pm.max_requests = 500
listen.backlog = 511 # 排队的连接上限

4.3 max_children 到底该设多少

不能拍脑袋写大,因为它直接决定内存占用。 计算方法是:

pm.max_children = 可用内存 / 单个 PHP 进程平均占用内存

先测单个 PHP 进程吃多少内存:

# 找一个正在运行的 php-fpm 子进程,看它的 RSS
ps -eo pid,rss,cmd | grep 'php-fpm: pool' | awk '{print $2/1024" MB"}' | sort -n | tail -5

假设单个进程平均 60 MB,服务器是 1G 内存,扣掉系统、Nginx、MySQL 占的约 400 MB,剩 600 MB 给 PHP-FPM:

600 / 60 = 10

所以我这台算出来就是 10,正好卡满,难怪一有并发就崩。解决思路不是无脑加大,而是:① 适当加大但留够余量;② 更要紧的是找出为什么单个进程吃内存/耗时长。

我最后是先把 max_children 调到 16,同时开慢日志找慢请求,双管齐下。

4.4 开启 PHP-FPM 慢日志找慢请求

很多 502 的根因是「个别请求特别慢,把 worker 长期占住」。开慢日志就能揪出来:

在 www.conf 里加:

request_slowlog_timeout = 5s
slowlog = /www/server/php/82/var/log/php-fpm-slow.log

重启后,tail -f 慢日志,就能看到卡在哪一行代码:

tail -f /www/server/php/82/var/log/php-fpm-slow.log

我这边揪出来的慢请求集中在 WordPress 的插件搜索,日志里能看到卡在 wp_posts 表的 LIKE '%关键词%' 查询上。给搜索字段加索引、装个缓存插件后,慢请求基本消失。

4.5 backlog 太小也会 502

listen.backlog 是内核层面允许排队的连接数。并发一高、worker 全忙时,超出的连接会进 backlog 队列等着。队列也满了,新连接就被拒,表现同样是 Resource temporarily unavailable。

适当调大:

listen.backlog = 2048

同时确认系统的 somaxconn:

sysctl net.core.somaxconn
# 太小就永久调大
echo 'net.core.somaxconn = 32768' >> /etc/sysctl.conf
sysctl -p

4.6 socket 路径不一致的坑

有一类 502 特别隐蔽:Nginx 里写 fastcgi_pass unix:/tmp/php-cgi-82.sock,但 PHP-FPM 实际监听的 socket 路径是另一个(比如升级 PHP 版本后路径变了),两边对不上,Nginx 就永远连不上。

# 看 PHP-FPM 实际监听的是哪个 socket
grep '^listen' /www/server/php/82/etc/php-fpm.d/www.conf

# 看这个 socket 文件到底存不存在
ls -l /tmp/php-cgi-82.sock

两边必须完全一致。这个坑我在升级 PHP 版本时踩过一次,改了 Nginx 配置里的版本号才对上。

五、超时类 502 的排查

如果日志是 upstream timed out,方向就不在进程数,而在后端处理太慢。

5.1 先确认超时阈值

location ~ \.php$ {
fastcgi_connect_timeout 60s;
fastcgi_send_timeout 60s;
fastcgi_read_timeout 60s; # 最常触发的是这个
...
}

fastcgi_read_timeout 是「等 PHP 返回结果」的最长时间,超过就 502。

5.2 定位到底慢在哪

按 SQL → 代码 → 外部请求的顺序查:

# 1. MySQL 慢查询日志
tail -n 50 /www/server/data/mysql-slow.log

# 2. 实时看 MySQL 当前在跑什么
mysql -uroot -p -e "SHOW FULL PROCESSLIST;"

# 3. 看是不是在等外部接口(比如调用第三方 API 卡住)
strace -p <php-fpm-pid> -e trace=network

我这台的根因就是上面说的慢查询。注意:调大超时只是兜底,不是解药,真正的解法是让后端变快。

六、上游是后端应用(proxy_pass)时的 502

如果站点是反代后端服务(Java / Node / Python),排查思路类似:

# 1. 后端进程还在不在
systemctl status your-app
ps -ef | grep your-app | grep -v grep

# 2. 端口有没有在监听
ss -lntp | grep ':8080'

# 3. 直接本地请求后端,绕过 Nginx,看后端自己是不是正常
curl -v http://127.0.0.1:8080/

如果 curl 本地就报错,那问题 100% 在后端,跟 Nginx 无关。常见原因:应用启动失败、被 OOM Killer 杀掉、连接池耗尽。

6.1 排查是否被 OOM 杀掉

后端连接被重置(Connection reset by peer)时,很可能是进程被系统内存回收杀掉了:

dmesg -T | grep -i 'killed process'
journalctl -k | grep -i oom

如果是 OOM,就得回到「内存调优」那条线去解决(缩小 JVM 堆、限制进程内存、加 swap 等)。

七、通用排查流程图(照着走就行)

整理成一句话版本,方便应急时按顺序走:

看到 502
├─ 1. tail -f Nginx error.log,抓关键词
├─ 2. 关键词是 Resource temporarily unavailable
│ → PHP-FPM 进程满:查 pm.max_children、backlog
├─ 3. 关键词是 Connection refused / No such file
│ → 上游没起 / socket 路径不一致:查进程和监听
├─ 4. 关键词是 upstream timed out
│ → 后端慢:查慢查询、慢日志、外部接口
├─ 5. 关键词是 Connection reset by peer
│ → 进程崩溃 / OOM:查 dmesg、journalctl
└─ 6. 无报错但偶发
→ 抓并发高峰时段、开慢日志、看监控

八、怎么让 502 不再复发

解决单次 502 容易,难的是别让它反复出现。我做了这几件事:

1. 加监控告警,不要等用户反馈才知道:

# 简单版:定期本地请求,非 200 就记日志
cat > /root/check_502.sh <<'EOF'
#!/bin/bash
code=$(curl -o /dev/null -s -w '%{http_code}' -m 10 http://127.0.0.1/)
if [ "$code" != "200" ]; then
echo "$(date '+%F %T') 站点异常,状态码: $code" >> /root/502.log
fi
EOF
chmod +x /root/check_502.sh

# 每分钟跑一次
echo '* * * * * /root/check_502.sh' | crontab -

2. 给 PHP-FPM 开状态页,实时看进程忙闲:

location ~ ^/status$ {
allow 127.0.0.1;
deny all;
fastcgi_pass unix:/tmp/php-cgi-82.sock;
fastcgi_param SCRIPT_FILENAME $fastcgi_script_name;
}

# www.conf 里打开
pm.status_path = /status

然后 curl http://127.0.0.1/status 就能看到 active processes(当前忙的 worker 数)和 max children reached(是否撞过上限),这两个数字是判断要不要扩容的直接依据。

3. 给 WordPress 上缓存,从源头减少 PHP 请求量。对象缓存 + 页面缓存装好之后,同样的访问量下 PHP-FPM 压力能降一大截。

4. 单机扛不住就上负载:Nginx 前面再多加几台 PHP 上游,upstream 里配置多节点,同时用 proxy_next_upstream 让一台挂了自动切另一台:

upstream php_backend {
server 127.0.0.1:9000;
server 127.0.0.1:9001;
}

九、踩坑总结

  1. 502 不等于 Nginx 挂了,先看 error.log 确定上游是谁,别张口就重启 Nginx。
  2. Resource temporarily unavailable = PHP-FPM 满了,这是最高频的 502 原因,重点查 pm.max_children 和 backlog。
  3. max_children 按内存算,别拍脑袋写 100:算错了直接把机器撑爆,反而更容易 OOM 出 502。
  4. 升级 PHP 版本后务必核对 socket 路径,Nginx 和 PHP-FPM 两边不一致是最隐蔽的 502。
  5. 调大超时是兜底不是解药,upstream timed out 的真正解法是让后端变快(查慢 SQL)。
  6. Connection reset by peer 要查 OOM,进程被杀而不是 Nginx 拒绝。
  7. 开 PHP-FPM 慢日志,这是找偶发 502 根因最有效的工具,没有之一。
  8. 加监控和状态页,让 502 在用户反馈之前就被发现,比事后救火重要得多。
  9. 偶发 502 一定要看并发高峰时段,空跑的时候一切正常,压测/晚高峰才现原形。
  10. 配置改完都要 nginx -t 和重载验证,别把配置写错造成更大的故障。

结语:502 看起来吓人,其实是一类「上游连不上或响应无效」的统一表现。把 error.log 的关键词和上面的对照表一匹配,方向基本就定了;剩下就是按进程数 / 路径 / 超时 / OOM 四条线去挖根因。真正花时间的从来不是修好这一次,而是找到「为什么会有这一次」并加监控防住它。

(本文为个人运维经验记录,环境与数据均为实际测试所得,供技术交流参考。)

posted @ 2026-11-03 17:04  前端已挂  阅读(5)  评论(0)    收藏  举报