LNMP 502 BAD GATEWAY

LNMP 502 BAD GATEWAY

502 错误的本质是:Nginx 作为网关成功接收了你的访问请求,但无法从后端服务(通常是 PHP-FPM 或 MySQL)获取到有效的响应

1.紧急恢复

后端进程(如 PHP-FPM)卡死或崩溃是导致 502 最常见的原因。所以

重启 PHP-FPM(请根据实际安装的 PHP 版本替换,如 8.2、8.3 等):

systemctl restart php-fpm

重启 Nginx(清空积压的请求队列):

nginx -s reload

重启 MySQL(如果数据库挂了也会导致后端无响应):

systemctl restart mariadb

刷新浏览器,看站点是否恢复。

2. 精准排查(看日志定位原因)

如果重启后依然报 502,不要盲目改配置,先看日志

2.1.查看 NGINX日志:

tail -50 /var/log/nginx/error.log

根据日志里的报错信息对症下药:

  • connect() failed (111: Connection refused):后端服务(PHP-FPM)没启动、端口没监听或被防火墙挡住。
  • connect() to unix:/var/run/php/php8.x-fpm.sock failed (2: No such file or directory):Nginx 配置中的 Socket 路径与 PHP 版本不匹配(例如刚升级过 PHP 版本)。
  • upstream timed out (110: Connection timed out):后端处理太慢,超时了(可能是插件死循环或数据库慢查询)。
  • upstream prematurely closed connection:后端程序直接崩溃/闪退了(可能是内存溢出 OOM 或代码致命错误)。
tail -n 200 /usr/local/nginx/logs/error.log | grep -i upstream

常见几种:

  • connect() failed (111: Connection refused) → PHP-FPM 没起、listen 地址不对、socket 文件丢失/权限错
  • upstream timed out (110: Connection timed out) → PHP 处理太慢,Nginx 等超时了,往往是慢插件/慢 SQL/外呼 API
  • recv() failed (104: Connection reset by peer) → PHP worker 中途崩了/超内存/被 OOM 杀/执行时间到被终止
  • upstream prematurely closed connection → FPM worker 主动断了,可能是 memory_limit、max_execution、segfault

顺手确认 Nginx 的 fastcgi_pass 和 PHP-FPM 的 listen 完全一致(unix socket 对 unix socket,tcp 对 tcp),socket 文件属主属组要让 Nginx 用户能读能写。

2.2.查看 PHP-FPM 日志:

源码安装通常在 php-fpm.conf 或 pool 配置里定义了 error_log,常见:

  • /usr/local/php/var/log/php-fpm.log
  • 或各 pool 的 www.conf 里配的日志路径
grep -iE "max_children|seems busy|oom|killed|memory|segmentation|signal" /usr/local/php/var/log/php-fpm.log

重点看:

  • server reached pm.max_children → worker 全忙,新请求进不来,最后 Nginx 超时报 502
  • seems busy → 并发超过空闲 worker,短期可加进程,长期要看慢请求
  • Out of memory / Allowed memory size exhausted → 单请求超 memory_limit,或整体内存不够
  • segfault / signal 11 → 某个 PHP 扩展崩了,记下来哪个 pool、什么时间点
tail -n 200 /usr/local/php/var/log/php-fpm.log

例如:

server reached pm.max_children setting (20):pm.max_children设的是20,峰值请求一来,20个FPM进程全被占满,没有空闲进程处理新请求,Nginx等不到响应直接报502。

seems busy … there are 0 idle:不仅总进程不够,初始空闲进程也设得太低,请求一来连预启动的进程都没有,临时孵化子进程也最多只能到20,依然不够用,所以反复报忙。

最后一行14-Aug的terminating是之前重启PHP的记录,重启后进程池重置,所以暂时恢复,但并发一来又会打满,和你说「重启就好」的现象完全吻合。

3.确认是不是进程池被打满

3.1.看当前 FPM 进程数和状态

ps -eo pid,ppid,rss,cmd | grep "[p]hp-fpm" | awk '{print $3/1024" MB\t"$4}'
ps aux | grep "[p]hp-fpm" | wc -l

如果活跃 worker 数长期等于 pm.max_children,且空闲为 0,就是打满。

4.查数据库是否把 PHP 拖死

WordPress 每个动态请求都查库,FPM 打满有时是 DB 慢。进 MySQL:

SHOW GLOBAL STATUS LIKE 'Threads_connected';
SHOW VARIABLES LIKE 'max_connections';
SHOW FULL PROCESSLIST;
SHOW GLOBAL STATUS LIKE 'Slow_queries';

如果大量连接卡在 Sending dataLockedWaiting for table metadata lock,或者 Threads_connected 接近 max_connections,就是数据库瓶颈。再开 MySQL 慢查询日志,拿慢 SQL 去加索引/改查询/加缓存。

5.按内存算 pm.max_children

5.1.调整 PHP-FPM 配置

公式:

max_children ≈ 留给PHP的内存量 / 单worker平均RSS

单 worker 平均 RSS 用实际采样:

ps --no-headers -o rss,cmd -C php-fpm | awk '{s+=$1;n++} END{if(n)printf "avg %.1f MB\n",s/n/1024}'
avg 51.3 MB(根据命令情况)

实时看单个 FPM worker 的 RSS

# 所有 worker 按内存倒序,单位 KB 的 RSS 在第1列
ps --no-headers -o rss,pid,cmd -C php-fpm | sort -nr | head

只统计 worker(排除 master):

ps -eo pid,rss,cmd | grep "php-fpm: pool" | grep -v grep

查看内存情况

free -h
  • 假设服务器总内存为 1.7G,当前可用内存(available)为 1.2G,Swap 使用了 128M。整体内存压力不大,没有发生严重的内存耗尽。结合之前单进程峰值约 70MB 的情况,为你计算安全的配置参数:

    内存评估与参数计算

    • 内存预留:1.7G 总内存需预留空间给系统、Nginx、MySQL 及缓存,建议留留 1GB 给 PHP、峰值 70MB → 1024/70≈14,留余量设 12;你 1.7G 总内存、系统+Nginx+MySQL 预留后若给 PHP 约 800MB~1GB,按 70MB 峰值算 11~14 比较安全。
    • 计算 max_children1024MB ÷ 70MB(单进程峰值)≈ 14.6
    • 安全设定:为防止内存泄漏导致 OOM,建议将 pm.max_children 设为 12

配置文件通常在 /usr/local/php/etc/php-fpm.d/www.conf/usr/local/php/etc/php-fpm.conf,打开后修改以下参数:

; 开启状态页路径
pm.status_path = /fpm-status
; 开启慢日志记录(路径可根据实际安装目录调整)
slowlog = /usr/local/php/var/log/php-fpm-slow.log
; 请求超过5秒判定为慢请求,记录堆栈
request_slowlog_timeout = 5s
; 单个请求最长执行60秒,防止卡死占着进程
request_terminate_timeout = 60s

; 进程管理模式,保持dynamic即可,比static更灵活
pm = dynamic
; 最大进程数,按上面的计算填
pm.max_children = 12
; 启动时的初始进程数,原来默认可能是5,调大避免请求一来还要临时孵化
pm.start_servers = 5
; 最小空闲进程数,保证突发请求有空闲进程可用
pm.min_spare_servers = 3
; 最大空闲进程数,避免空闲进程占太多内存
pm.max_spare_servers = 8
; 每个进程处理500个请求后自动回收,预防WordPress插件内存泄漏(可选但建议加)
pm.max_requests = 500
; 单个请求最长执行60秒,防止慢请求卡死占着进程不放(可选)
request_terminate_timeout = 60s

保存后测试 Nginx 配置并重载:

nginx -s reload

保存后重启php-fpm 配置并重载:

/usr/local/php/sbin/php-fpm -t
systemctl restart php-fpm

6.配置验证

6.1. 验证FPM进程配置是否生效

改完php-fpm.conf后,不用开状态页,直接看进程数是否符合配置:

# 看当前FPM总进程数(包含master+worker,worker数≈你设的start_servers)
ps aux | grep php-fpm | grep -v grep | wc -l

# 看单个worker进程内存占用(确认是否还在70MB峰值内)
ps aux | grep php-fpm | grep -v grep | awk '{print $6/1024 "MB"}' | sort -n

# 看总内存占用(12个进程*70MB≈840MB,远低于你1.2G的可用内存,不会OOM)
ps -o pid,rss,cmd -C php-fpm | awk '{s+=$2} END{print "FPM总内存占用:"s/1024/1024"GB"}'

正常表现:启动后worker进程数≈pm.start_servers(你设的5),峰值不会超过pm.max_children(12),总内存占用不超过1GB。

6.2.实时监控FPM负载(替代web状态页)

watch命令实时刷新,不用开任何监控接口:

# 实时看FPM进程数变化(并发上来时会涨,最高到12)
watch -n 1 'ps aux | grep php-fpm | grep -v grep | wc -l'

# 实时看FPM错误日志(有没有再报max_children/busy)
watch -n 1 'tail -n 3 /usr/local/php/var/log/php-fpm.log'

# 实时看内存余量(确保不会吃完)
watch -n 1 'free -h'

7.压力测试:本地模拟并发,验证配置扛不扛得住

# 模拟20个并发请求(超过你设的12个进程,测试会不会打满)
for i in {1..20}; do curl -s http://127.0.0.1 >/dev/null & done

# 同时看FPM进程数,最高到12就说明配置生效,不会无限涨
ps aux | grep php-fpm | grep -v grep | wc -l

正常表现:20个并发下,FPM日志不会再报max_children,网站访问正常,不会502。

 

Comments

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注