Nginx 502 Bad Gateway 排查实战:从日志到进程的完整诊断思路

对于个人站长来说,Nginx 502 Bad Gateway 应该是仅次于 404 的"老朋友"了。网站正跑得好好的,突然整站打不开,浏览器里冒出白底黑字的一行 502,第一反应是慌,第二反应是到处乱试:重启一下 Nginx、重启一下 PHP、刷新一下页面……运气好恢复了,但根本不知道问题出在哪,下次照样犯。这篇文章不教你怎么"碰运气",而是给你一套从日志到进程的完整排查思路,让你遇到 502 时能按图索骥,十分钟内定位根因,并且知道怎么从配置上预防它再犯。

一、先弄懂 502 到底是什么

Nginx 本身不负责执行 PHP 代码,它只做两件事:接收用户请求、把请求转发给后端程序(最常见的后端是 PHP-FPM,也可能是 Node.js、Java 或者另一台服务器),然后把后端返回的结果再回给用户。这个"转发"的角色在 HTTP 协议里叫反向代理,而 502 Bad Gateway 的含义就是:Nginx 把请求转给了后端,但后端没有给出有效的响应。

注意 502 和 504 的区别:502 是"后端根本没答上话"(进程挂了、连接被拒、响应不合法),504 Gateway Timeout 是"后端答得太慢,超过了 Nginx 设定的等待时间"。两者原因不同,排查方向也不同,别混为一谈。另外还有一个常见的 503 是 Nginx 自己在限流或维护,跟后端无关。

二、第一步永远先看 Nginx 错误日志

排错的第一原则是看日志,而不是猜。Nginx 的错误日志默认在 /var/log/nginx/error.log,路径可以在 nginx.conf 里用 error_log 指令确认和修改。查看最近的报错:

tail -n 100 /var/log/nginx/error.log

日志里会出现几类典型的关键句,每一句都直接指向不同的病因:

  • connect() failed (111: Connection refused) while connecting to upstream —— 后端服务没在监听,最常见的两个原因:PHP-FPM 挂了,或者 fastcgi_pass 写的端口/路径不对;
  • upstream prematurely closed connection while reading response header from upstream —— 后端进程在处理请求的过程中被杀死,常见于 PHP-FPM 进程崩溃、被 OOM Killer 干掉、或 worker 达到 max_requests 上限被回收时正好在处理请求;
  • upstream timed out (110: Connection timed out) while reading response header —— 后端处理太慢,超过了 fastcgi_read_timeout,这种其实更容易表现为 504;
  • connect() to unix:/run/php/php8.1-fpm.sock failed (13: Permission denied) —— socket 文件权限问题,Nginx 的 worker 进程没有访问 socket 的权限。

先看日志再动手,方向就不会错。很多人一上来就重启,日志都没瞄一眼,结果重启完还是 502,白白浪费时间。

三、第二步:确认 PHP-FPM 还活着吗

日志里出现 Connection refused,十有八九是 PHP-FPM 挂了。用 systemd 管理的系统直接查服务状态:

systemctl status php8.1-fpm

如果服务是 failed 状态,看最后几行日志能知道崩溃原因。如果服务还在运行,但进程数不对,可以用 ps 确认:

ps aux | grep php-fpm

正常情况下你会看到 1 个 master 进程和若干个 worker 进程(进程数由 pm 配置决定)。如果只有 master 没有 worker,说明 worker 全部异常退出,这时候去翻 PHP-FPM 自己的错误日志,通常在 /var/log/php8.1-fpm.log,里面有 worker 退出的具体原因,比如内存分配失败(out of memory)或者某个扩展崩溃。

四、第三步:排查高频病因——进程耗尽与内存不足

个人站长的 VPS 普遍只有 1G 到 4G 内存,502 的元凶里,进程耗尽和内存不足占了大多数。先看内存:

free -h

再看有没有 OOM Killer 出手杀进程:

dmesg | grep -i -E 'oom|killed process' | tail -20

如果看到 php-fpm 相关的 killed process,基本可以断定是内存不够被系统强杀。这时候要么升级内存,要么把 PHP-FPM 的进程数调小。进程数的关键配置在 /etc/php/8.1/fpm/pool.d/www.conf:

pm = dynamic
pm.max_children = 10
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
pm.max_requests = 500

max_children 决定 PHP-FPM 最多同时开多少个 worker,每个 worker 常驻内存,以 2G 内存、单进程平均占 50M 计算,理论上限约 40 个,但还要给 MySQL、Nginx、系统本身留内存,所以 2G 内存的机器设 10 到 15 比较稳妥。另一个非常值得开的是 pm.max_requests,它让每个 worker 处理满 500 个请求后自动重启,能有效避免某些扩展造成的内存泄漏——内存泄漏的典型表现是:网站刚重启时一切正常,跑几天后越来越卡,最后 502。另外慢日志也建议打开,方便定位是哪个脚本拖垮了进程:

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

五、超时配置与权限问题

如果日志显示 upstream timed out,说明某个请求执行太久(比如导出大文件、调用外部接口超时),超过了 Nginx 的等待时间。检查站点配置里的这几个参数:

fastcgi_connect_timeout 5s;
fastcgi_send_timeout 60s;
fastcgi_read_timeout 60s;

个人站长的建议是:connect_timeout 别设太大,5 秒足够,设大了反而让故障"隐身";read_timeout 和 send_timeout 按业务需要调,一般 60 秒足够。如果某个接口确实要跑很久,优先优化代码,而不是无限调大超时。权限问题则常见于用 Unix Socket 通信的场景:Nginx worker 和 PHP-FPM 必须能同时读写 socket 文件。检查 php-fpm 配置里的 listen 和 listen.owner/listen.group/listen.mode,确保 Nginx 运行用户(通常是 www-data 或 nginx)在组内,mode 一般设 0660 或 0666。

六、把排查命令串成一套组合拳

把上面的步骤整理成一条龙,遇到 502 按顺序执行:

# 1. 看 Nginx 报错
tail -n 50 /var/log/nginx/error.log
# 2. 服务与进程状态
systemctl status php8.1-fpm
ps aux | grep php-fpm | wc -l
# 3. 内存与 OOM
free -h
dmesg | grep -i oom | tail -5
# 4. 端口/监听确认
ss -lntp | grep 9000
# 5. 手动请求后端,绕开 Nginx 判断问题是否在后端
curl -I http://127.0.0.1:9000/status
# 6. 磁盘空间与 inode(这一条最容易忽略)
df -h
df -i

第 5 条需要先开启 PHP-FPM 的 status 页:在 www.conf 里设置 pm.status_path = /status,然后在 Nginx 里加一个 location 转发过去。status 页面会告诉你当前活跃连接数、队列长度、空闲进程数,是判断"进程够不够用"最直观的依据。

第 6 条的磁盘检查很多人会漏掉,但它确实是 502 的隐藏元凶:磁盘写满之后,Nginx 无法追加 access.log、PHP-FPM 无法写 session 和临时文件,整个请求链路直接断裂,表现就是大面积的 502 或者白屏。inode 耗尽更隐蔽——df -h 看着还有空间,但 df -i 显示 inode 用完了,通常是某个目录堆积了海量小文件(比如没开 logrotate 的日志目录、缓存目录),一样会让服务写不进文件。发现这两种情况,清理日志、清理缓存,必要时扩容磁盘,问题立刻缓解。

七、实战案例:一次 502 的完整排查过程

光讲概念容易记不住,用一个真实场景把上面的方法串一遍。假设你的博客早上八点开始整站打不开,浏览器一律 502。按顺序执行:

第一步,tail -n 100 /var/log/nginx/error.log,看到大量 connect() failed (111: Connection refused) while connecting to upstream,说明后端 PHP-FPM 根本没在监听,问题不在 Nginx。第二步,systemctl status php8.1-fpm,状态是 failed,服务已经异常退出。第三步,看 PHP-FPM 自己的日志(Debian 系在 /var/log/php8.1-fpm.log),翻到最后几行,发现一条 PHP Fatal error: Allowed memory size of 134217728 bytes exhausted,也就是说某个请求把 128M 的内存限制吃爆了,进程直接崩溃退出。第四步,顺着这条线索打开慢日志和错误日志,定位到是某个插件在循环请求外部 API,每次请求都缓存结果到内存数组里,并发一高内存就炸。第五步,处理:先停用这个插件让网站恢复,再在 php.ini 里把 memory_limit 从 128M 调到 256M,同时确认 pm.max_requests 设为 500,让 worker 定期回收内存,最后 systemctl start php8.1-fpm 拉起服务。观察几天,内存曲线平稳,问题不再复发。

这个案例里,502 只是表象,真正的根因是代码级的内存问题。而你之所以能十分钟内定位到根因,靠的就是一层层看日志:Nginx 日志告诉你"后端没响应",服务状态告诉你"进程死了",PHP-FPM 日志告诉你"怎么死的"。排查 502 就像剥洋葱,每一层日志都是剥掉一层壳,剥到底就是真相。

八、预防比排查更重要

排查治标,预防治本。三个立竿见影的预防手段:第一,配置 logrotate 做日志轮转,避免日志文件撑爆磁盘(磁盘满了也会导致各种莫名其妙的故障);第二,写一个简单的健康检查脚本,定时检测首页 HTTP 状态码,连续几次非 200 就自动重启 php-fpm 并发告警,crontab 里每两分钟跑一次;第三,把 Nginx 和 PHP-FPM 的配置改动都走 git 管理,出问题能快速回滚,也方便对比"上次改了啥之后开始 502"。对于个人站长,502 不可怕,可怕的是每次都在瞎试。把日志读起来、把进程数调合理、把监控挂起来,这个错误基本就能和你和平相处了。

Last modification:August 19th, 2026 at 02:37 pm

Leave a Comment