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/外呼 APIrecv() 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 超时报 502seems 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 data、Locked、Waiting 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_children:
1024MB ÷ 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