上个月一个投教育类广告的团队把独立部署环境从 2 核 4G 临时升到 4 核 8G,流量上来后仍然每隔二十来分钟就冒一次 502。团队成员在“继续加机器”和“先查 PHP-FPM 参数”之间纠结。这个场景下的核心决策是:502/504 到底由哪一层先出现异常?本文给出的回答方向是:不要一上来就扩容,而要先按 Nginx 错误日志、PHP-FPM 进程池状态、慢日志与资源水位、参数调整与回归验证的顺序做一次 PHP-FPM 502/504 排查。
一、先读 Nginx 错误日志:502 和 504 是两个方向
可观察症状是访问独立部署分流系统时偶发 502 Bad Gateway 或 504 Gateway Time-out,且不是全量失败。排查第一步不是看 PHP-FPM 配置,而是从 Nginx 错误日志区分二者。502 通常对应 upstream sent invalid header while reading response header from upstream,或者 connect() failed、recv() failed,说明 Nginx 与 PHP-FPM 的连接建立了但 PHP-FPM 没有给出合法响应;504 对应 upstream timed out,说明 Nginx 在 fastcgi_read_timeout 窗口内没有等到 PHP-FPM 执行完。两者处理路径不同:502 优先看进程是否存活、进程数是否耗尽、socket 权限;504 优先看脚本执行时长、慢查询、外部回源等待。
检查步骤:
tail -n 200 /var/log/nginx/error.log | grep -E "502|504|upstream"。- 同时拉取 access log 中对应时间点的请求 URI 和 upstream_status,确认是集中在某几个路由还是全局偶发。
- 如果 access log 还显示 499,说明客户端先断开,这往往与 504 同源:执行过慢。
这里还涉及一个容易混淆的点:如果 Nginx 日志显示 connect() to unix:/run/php/php-fpm.sock failed (11: Resource temporarily unavailable),这属于 502 队列满的强信号,不是 504。不同信号对应不同排查路径。
二、查看 PHP-FPM 进程池:是否撞到 pm.max_children
第二步才是看进程池。开启 pm.status_path 后,可以用 curl http://127.0.0.1:9000/status?full 或通过 unix socket 访问状态页。重点看几项:active processes、idle processes、max children reached、listen queue。如果 max children reached 在持续增长,说明历史上已经多次撞到 pm.max_children 上限,请求进入 listen queue 等进程;如果队列也满,客户端就会看到 502。listen queue 非零说明瞬时并发超过进程处理能力。
某团队早期用 2 核 4G,把 pm.max_children 设成 10,日均一千二三点击时只是偶发,到了教育类晚高峰后每二十分钟就出现一次 502。后来先看状态页,发现 max children reached 已经几百次。这个案例说明不要凭“这点流量线程数不够吧”的直觉直接调大,而是先确认到底有没有撞限。
配置项建议不要一次改太多:pm = dynamic 适合中小流量环境,pm.max_children 需要按“单进程常驻内存 × 进程数 + Nginx + MySQL/Redis + 系统保留”估算,2 核 4G 常见从 10 调到 20 之前先算出内存余量。可参考LNMP初始化与组件锁定里关于组件版本和内存基线的内容。
三、开启 slowlog 定位慢执行:别把 504 当成进程问题
如果状态页显示进程并没有耗尽,问题就转向执行耗时。开启 PHP-FPM 的 slowlog 与 request_slowlog_timeout = 5s,会记录执行超过 5 秒的脚本堆栈。注意这个日志不是错误日志,要单独轮转,否则会占用大量磁盘。看 slowlog 时不要只看文件名,还要看堆栈里的方法名、数据库连接或 curl 调用位置。分流系统里常见三类慢来源:规则匹配时请求了外部 API 且没有设置超时;页面模板里串行请求了多个三方资源;数据库查询没有索引导致全表扫描。
还要同步看系统资源水位:free -m 看内存与 swap,top -bn1 | head -20 看 CPU 和 load average,df -h 看磁盘,iostat -x 1 10 看 IO。502/504 不一定只是 PHP-FPM 问题,磁盘 IO 高也会拖慢一切。检查 ulimit -n 是否过低,文件句柄不足会造成 connect() failed 类 502。如需补齐状态码层面的判断,可继续看链路状态码核验清单。
四、参数调整与回归验证:每次只动一个变量
排查到原因后,进入收尾验证。调整参数时遵守“一次只动一类变量”:例如先调 pm.max_children,重载 PHP-FPM 并观察 10 分钟,再调 fastcgi_read_timeout。执行 php-fpm -t 检验配置语法,再 service php-fpm reload,Nginx 侧 nginx -t && nginx -s reload。如果是 unix socket 权限问题,确认 Nginx 与 PHP-FPM 用户一致、socket 目录可写。
回归验证不要只看 error log 清空。需要同时确认三件事:max children reached 不再新增;对应业务 URI 的错误率回到既有基线;响应时间稳定在业务可接受区间。可以短时间开启 access_log 增加 $upstream_response_time 记录,观察 95 分位是否下降。不要拿一两次手动刷新当作验证结论。这里与LNMP初始化与组件锁定中的健康检查动作可以形成上线前检查闭环。
小结
PHP-FPM 502/504 排查的顺序价值在于:先看 Nginx 错误日志能把问题二分为“连接层失败”和“执行层超时”,再看进程池状态确认是否撞限,最后用 slowlog 和资源水位定位慢执行来源。每次只调整一类参数并用三项指标回归验证,能避免把偶发 502 修成长期 504。
常见问题
PHP-FPM 502 和 504 有什么区别?
502 通常表示 Nginx 与 PHP-FPM 之间连接失败、进程退出或进程数耗尽,返回前没有拿到合法响应;504 表示 Nginx 在 fastcgi_read_timeout 时间内没有等到 PHP-FPM 执行完成。排查时先把两者分开,可以少走很多弯路。
pm.max_children 设置多少合适?
需要结合单进程常驻内存、服务器总内存和是否同时运行 MySQL、Redis 来估算,不能只看 CPU 核数。中小流量环境可以先估算单进程约 30 到 60MB,再用空闲内存除以单进程内存得出保守值,并留出系统保留空间。
开启 slowlog 会影响性能吗?
正常 Only 当请求超过 request_slowlog_timeout 时才记录堆栈,对绝大多数快速请求没有明显影响。但如果阈值设得太低,比如 1 秒,慢日志写入量和磁盘 IO 会上升,反而可能干扰诊断。
fastcgi_read_timeout 设多少秒合理?
普通页面通常 30 秒已经足够,涉及外部 API 回源或批量数据处理的脚本可以单独放大到 60 秒。不要一开始就设成 300 秒,否则执行过慢的请求会长期占用进程,最终把进程池拖垮。
502/504 排查一定先重启 PHP-FPM 吗?
不一定。第一步应先看 Nginx 错误日志和 PHP-FPM 状态页,确认是进程数耗尽、慢执行还是 socket 权限问题。如果盲目重启,可能暂时恢复但根因还在,高峰时问题会重新出现。
延伸阅读
本文把 PHP-FPM 502/504 的排查顺序讲完后,如果你还需要从插件层面对比不同跳转方式在延迟和稳定性上的差异,可以继续阅读:跳转插件方案对比与选型复盘。