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-FPMproxy_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;
}九、踩坑总结
- 502 不等于 Nginx 挂了,先看 error.log 确定上游是谁,别张口就重启 Nginx。
Resource temporarily unavailable= PHP-FPM 满了,这是最高频的 502 原因,重点查pm.max_children和backlog。max_children按内存算,别拍脑袋写 100:算错了直接把机器撑爆,反而更容易 OOM 出 502。- 升级 PHP 版本后务必核对 socket 路径,Nginx 和 PHP-FPM 两边不一致是最隐蔽的 502。
- 调大超时是兜底不是解药,
upstream timed out的真正解法是让后端变快(查慢 SQL)。 Connection reset by peer要查 OOM,进程被杀而不是 Nginx 拒绝。- 开 PHP-FPM 慢日志,这是找偶发 502 根因最有效的工具,没有之一。
- 加监控和状态页,让 502 在用户反馈之前就被发现,比事后救火重要得多。
- 偶发 502 一定要看并发高峰时段,空跑的时候一切正常,压测/晚高峰才现原形。
- 配置改完都要
nginx -t和重载验证,别把配置写错造成更大的故障。
结语:502 看起来吓人,其实是一类「上游连不上或响应无效」的统一表现。把 error.log 的关键词和上面的对照表一匹配,方向基本就定了;剩下就是按进程数 / 路径 / 超时 / OOM 四条线去挖根因。真正花时间的从来不是修好这一次,而是找到「为什么会有这一次」并加监控防住它。
以上就是Nginx 502 Bad Gateway的完整排查指南(PHP-FPM / 上游 / 超时三类原因全覆盖)的详细内容,更多关于Nginx 502 Bad Gateway完整排查的资料请关注脚本之家其它相关文章!
相关文章
Debian系统下为PHP程序配置Nginx服务器的基本教程
这篇文章主要介绍了Debian系统下为PHP程序配置Nginx服务器的基本教程,这里使用到了FastCGI和php-fpm,需要的朋友可以参考下2015-12-12
详解nginx 中location和 proxy_pass的匹配规则
location是Nginx中用来匹配客户端请求URI的指令,决定如何处理特定路径的请求,它定义了请求的路由规则,后续的配置(如 proxy_pass)会应用在匹配的请求上,这篇文章主要介绍了nginxlocation和proxy_pass的匹配规则,需要的朋友可以参考下2025-04-04
基于nginx实现上游服务器动态自动上下线无需reload的实现方法
这篇文章主要介绍了基于nginx实现上游服务器动态自动上下线无需reload,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下2021-02-02
SQL操作Pandas DataFrame的三种方式示例详解
这篇文章主要为大家介绍了SQL操作Pandas DataFrame的三种方式示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪2023-08-08


最新评论