Nginx 反向代理 PHP-FPM 返回 502 Bad Gateway:并发慢请求打满 Worker 池的排查与修复
日期:2026-08-29 | 技术栈:Nginx、PHP-FPM、FastCGI、Docker、docker-compose 报错关键字:
502 Bad Gateway、504 Gateway Time-out、server reached pm.max_children setting、upstream timed out、value is NULL for a ZEND_INI_PARSER_ENTRY、host not found in upstream
一、问题背景
生产环境架构为 客户端 → Nginx(80) → PHP-FPM(9000),Nginx 通过 FastCGI 把 .php / /api/ 请求转发给后端 PHP-FPM。
上线后当多个用户同时访问一个需要 5 秒才能返回的慢接口 /api/slow.php 时,几乎所有 PHP 请求都开始返回 502 Bad Gateway / 504 Gateway Time-out,只有静态文件 /static/hello.txt 正常。
二、复现步骤
cd scenario/2026-08-29-0000
docker compose up -d
# 1) 正常请求(应能通)
curl http://localhost:8080/
curl http://localhost:8080/api/index.php
# 2) 10 个并发慢请求触发故障
for i in $(seq 1 10); do
curl -s -o /dev/null -w "req-$i: %{http_code}\n" http://localhost:8080/api/slow.php &
done
wait
# 3) 随后访问快速接口也失败
curl -i http://localhost:8080/api/index.php
# 4) 看日志
docker compose logs nginx
docker compose logs php-fpm
复现输出:并发慢请求全部 504,随后快速接口也是 504,php-fpm 日志出现
WARNING: [pool www] server reached pm.max_children setting (2), consider raising it。
三、根本原因
两个层面叠加导致:
- Nginx 超时太短:
fastcgi_read_timeout 2s,而慢接口需处理 5s。Nginx 等不到响应即返回504 Gateway Time-out(upstream timed out)。 - PHP-FPM Worker 池太小 + 无兜底:
pm.max_children = 2,同时只能处理 2 个请求。慢请求把 2 个 worker 全部占住,后续所有 PHP 请求(包括快速接口)排队/被拒,Nginx 表现为502/504。- 无
request_terminate_timeout→ 超慢请求不会被杀掉。 - 无
pm.max_requests→ worker 长期不重启,易内存泄漏/僵死。
- 无
额外环境坑:PHP-FPM 不支持 # 注释
初始 php/www.conf 使用 # 作为注释,导致 php-fpm 直接启动失败:
ERROR: [/usr/local/etc/php-fpm.d/www.conf:1] value is NULL for a ZEND_INI_PARSER_ENTRY
ERROR: FPM initialization failed
进而 Nginx 报 host not found in upstream "php-fpm"。PHP-FPM 池配置应使用 ; 注释,# 不被其 INI 解析器支持。
四、解决办法
修改 php/www.conf(PHP-FPM worker 池)
; 注释必须用 ';',不能用 '#'
[www]
listen = 9000
pm = dynamic
pm.max_children = 16 ; 2 -> 16,容纳更多并发
pm.start_servers = 4
pm.min_spare_servers = 2
pm.max_spare_servers = 6
request_terminate_timeout = 30s ; 杀掉超过 30s 的慢请求
pm.max_requests = 500 ; worker 处理 500 次后重启,防泄漏
修改 nginx/default.conf(FastCGI 反代)
location ~ \.php$ {
fastcgi_pass php-fpm:9000;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_keep_conn on; ; 复用上游连接
fastcgi_read_timeout 60s; ; 2s -> 60s,> 慢接口 5s
fastcgi_send_timeout 60s;
fastcgi_connect_timeout 5s;
fastcgi_buffer_size 16k;
fastcgi_buffers 8 16k;
}
五、验证
修复并重启后:
docker compose down && docker compose up -d
# 10 个并发慢请求:全部 200,耗时约 5s
for i in $(seq 1 10); do curl -s -o /dev/null -w "slow-%{http_code} " http://localhost:8080/api/slow.php & done; wait; echo
# 慢请求处理中访问快速接口:全部 200
for i in $(seq 1 5); do curl -s -o /dev/null -w "fast-%{http_code} " http://localhost:8080/api/index.php & done; wait; echo
# 50 并发压测:全部 200
for i in $(seq 1 50); do curl -s -o /dev/null -w "req-%{http_code} " http://localhost:8080/api/slow.php & done; wait; echo
# 无 502/504
docker compose logs nginx | grep -E '" (502|504) ' || echo "无 502/504"
docker compose logs nginx | grep -c "upstream timed out" || echo "0 个 upstream 超时"
结果:并发慢请求、快速接口、50 并发压测全部返回 200;nginx 日志无 upstream timed out,无 502/504。
六、经验与避坑
502vs504:504 Gateway Time-out= Nginx 等上游超过fastcgi_read_timeout;502 Bad Gateway= 上游连接被拒 / worker 池耗尽。看 nginx error.log 关键字即可区分。server reached pm.max_children setting是 worker 池打满的最直接信号,出现即应调大pm.max_children或优化慢请求。- PHP-FPM 池配置注释必须用
;,#会报value is NULL,这是最容易忽略的坑。 - 慢请求治理三板斧:调大
fastcgi_read_timeout、调大pm.max_children、设置request_terminate_timeout+pm.max_requests。三者配合既能承载并发,又能兜底杀超时、防内存泄漏。 - 开启
fastcgi_keep_conn可复用 FastCGI 连接,减少高并发下的握手开销。 - 排查时先做最小复现:单独
docker run ... php-fpm -t验证配置语法,再用 curl 并发脚本定位性能问题,避免环境问题与业务问题相互干扰。
