PHP-FPM 子进程打满导致 502 怎么排查:从 Nginx 错误日志到 pm.max_children 调整
来源:17golang原创
时间:2026-07-09 10:55:28 407浏览 收藏
线上 PHP 站点突然出现 502,大家最先想到的往往是 Nginx 出问题、机器带宽跑满或者某个业务接口挂了。但如果报错全都集中在访问高峰期,翻 Nginx 错误日志反复能看到 upstream timed out、connect() to unix:/run/php/php-fpm.sock failed,同时查 PHP-FPM 状态页发现空闲子进程数已经掉到 0,说明问题早就从单个慢请求蔓延开,变成了整个进程池的拥堵。碰到这种情况别上来就直接调大 pm.max_children,先把占住子进程的请求定位清楚,做出来的修复才不会是只能撑几分钟的临时对策。
- 502 报错如果伴随 PHP-FPM 空闲子进程为 0,优先按进程池拥堵排查,不用死磕 Nginx 配置。
pm.max_children可以快速止血,但根因几乎都出自慢 SQL、外部接口等待或资源锁竞争。- 要把慢日志、状态页数据、Nginx upstream 错误三类信息放到同一时间线里对照分析。
- 故障恢复建议分两步走:先压下错误率,再逐一清理长时间占用子进程的异常请求。
影响面:502 先从少量接口冒出来
这类故障几乎不会出现全站一秒钟全挂的情况。最常见的过程是订单列表、导出报表、支付回调这类本身就偏慢的接口先报 502,之后普通页面也逐渐开始响应变慢。因为 PHP-FPM 的子进程是所有请求共享的资源,一批请求被慢 SQL 或者外部接口调用卡住之后,新进来的请求只能排队,排队时长超过 Nginx 的网关等待上限就会直接抛出 502。
排查的第一步先理清楚用户侧的实际影响:哪些接口报了错、错误占比有多高、持续了多长时间、是不是只出现在某一个机房或者某几台机器上。摸清楚这些信息之后,才能判断后续操作是直接扩容、摘下线异常机器、做功能降级,还是直接回滚最近的上线版本。
| 现象 | 可能含义 | 优先排查位置 |
|---|---|---|
| 只有少量接口报 502 | 慢请求在局部堆积 | Nginx upstream 访问日志 |
| 全站所有请求响应都变慢 | FPM 子进程已经接近满载 | PHP-FPM status 状态页 |
| 机器 CPU 不高但请求持续堆积 | I/O 等待、锁等待或外部调用等待 | PHP 慢日志和数据库监控面板 |
| 重启 PHP-FPM 之后服务短暂恢复很快又报错 | 进程队列被清空但根原因没消除 | 故障恢复前后的完整时间线数据 |
时间线:先把 Nginx 和 PHP-FPM 的证据对齐
502 刚出现的时候,单拎某一条日志出来看很容易判断错方向。Nginx 只能感知到后端没在约定时间内返回数据,PHP-FPM 状态页能告诉你整个进程池是不是已经被占满,慢日志才能明确告诉你具体是哪些 PHP 请求卡住了。把三类信息放到同一分钟的时间轴里对照,根因很快就能显出来。

一个典型的故障时间线大概是这样:
10:02 订单列表 P95 从 420ms 升到 3.8s 10:04 Nginx 出现 502 Bad Gateway 10:05 PHP-FPM status: active processes 40, idle processes 0 10:06 slowlog 记录 /order/export.php 等待数据库返回 10:08 临时下线导出入口,502 错误率开始下降
这里有个很实用的判断点:如果当时机器 CPU 已经跑满,大概率是 PHP 代码本身计算逻辑太重;如果 CPU 占用率不高但 FPM 子进程已经全满,绝大多数情况是请求在等数据库返回、等 Redis 响应、等第三方接口回调或者等文件 I/O 操作。别被“机器资源使用率还很低”的表象误导,PHP-FPM 进程池占满之后,整个入口链路照样会被堵死。
触发条件:慢请求长期占住子进程
PHP-FPM 用的是进程池模型。每一个请求进入之后,会独占一个 worker 子进程,直到请求完全处理完才会释放资源。原本耗时几百毫秒的正常接口,一旦因为某些异常变到十几秒才能返回,几十个并发就足以把整个进程池占满。
先打开状态页确认是不是进程池层面的问题,相关配置可以放到内网管理端访问,或者只给本机开放权限,避免直接暴露在公网带来安全风险。
pm.status_path = /fpm-status ping.path = /fpm-ping request_slowlog_timeout = 3s slowlog = /var/log/php-fpm/www-slow.log
状态页里重点关注这几个字段:
active processes:正在处理请求的子进程数量。idle processes:空闲可用的子进程数量,长时间掉到 0 就要立刻警惕。listen queue:当前已经排队、等待进入进程池的请求数量。max children reached:进程池达到子进程上限的累计次数。
如果 max children reached 这个数值一直在上涨,说明已经有大量请求因为进程池容量不足开始排队,这个时候单纯重启 PHP-FPM 只能让服务恢复一小会,等之前的慢请求再次进来之后,还是会很快把进程池占满。
根因:容量不足只是表象,慢请求才是核心原因
pm.max_children 设置太小确实会放大故障影响,但它几乎不会是问题的唯一原因。真正要深挖的点是:为什么原本正常的单个请求耗时突然变长了?哪些请求占住了 worker 子进程?这些请求有没有必要在业务高峰期同步执行完?
慢日志可以直接看到卡住的 PHP 文件名和完整调用栈。比如导出接口里某条 SQL 漏加合适索引,把几十个导出请求的耗时拖到 8 秒以上,FPM 进程池很快就会被全部占满。
[pool www] pid 18492 script_filename = /srv/app/public/order/export.php request_uri = /order/export?status=paid duration = 8.42s PDOStatement->fetchAll() OrderExportService->buildRows() ExportController->download()
拿到慢日志之后,不要只笼统记录“导出接口运行慢”。更有参考价值的记录内容是对应接口名、SQL 指纹、扫描行数、锁等待状态、外部接口耗时、发生时间点和当时的并发量。这样后续的修复动作才能精准落到具体对象上,而不是泛泛说一句优化性能。
修复动作:先恢复服务可用性,再慢慢处理慢请求
故障处理可以拆成两步走。第一步先把 502 的错误占比压到可接受范围,第二步再慢慢处理慢 SQL、拆分同步队列、新增限流规则和把耗时长的逻辑后台化。生产环境里这两个动作经常需要并行推进,但要分清楚哪部分是临时止血,哪部分是彻底根治。

常用的止血操作包括:
- 临时调高
pm.max_children数值,前提是机器可用内存足够,别直接把机器撑到触发 OOM。 - 先把异常流量的机器摘出负载均衡,或者临时关掉高耗时的功能入口,比如大体积报表导出。
- 给高耗时接口加网关层面的限流,避免新的请求继续涌进来把整个进程池占满。
- 把可以异步处理的任务改成后台队列执行,前端只返回任务编号和后续进度查询入口。
容量调整别随手拍脑袋。先估算单个 PHP-FPM 子进程的常驻内存占用,再结合机器剩余可用内存设置上限。比如单个子进程峰值内存大概是 80MB,机器预留好系统和 Nginx 的内存占用之后,可以分给 FPM 6GB 内存,这种场景下 pm.max_children 就没必要随手配置到 200。
pm = dynamic pm.max_children = 80 pm.start_servers = 12 pm.min_spare_servers = 8 pm.max_spare_servers = 20 request_terminate_timeout = 30s
上面的数值只是示例,真实环境的配置要结合单个进程内存占用、日常流量峰值和接口平均耗时压测之后得出。尤其是 request_terminate_timeout 这个配置项,它可以拦截掉异常长的请求,但设置得太短也可能误伤正常的大文件上传或者导出场景。
防复发:把慢日志、容量基准和告警一起补上
这次 502 故障恢复之后,别只在故障单里写一句“已恢复”就结束。建议补三类优化项:容量基准数据、慢请求治理、告警阈值配置。不然下次高峰期换另一个接口变慢,还是会用同样的方式把 PHP-FPM 进程池拖垮。
- 容量基准:记录每台机器的
pm.max_children、单进程峰值内存、平均请求耗时。 - 慢请求治理:把慢日志里记录的接口按耗时和访问频次排序,优先改高峰期的同步链路逻辑。
- 数据库侧检查:定期巡检 SQL 指纹、扫描行数、锁等待情况和缺失的索引。
- 告警阈值:监控
idle processes、listen queue、max children reached指标。 - 降级开关:报表导出、批量刷新、第三方数据同步这类功能要留可以一键关闭的管理入口。
一个很实用的告警规则是:空闲子进程数值连续 2 分钟低于 2,或者 listen queue 计数持续上涨,就立刻提醒值班人员去查慢日志,别等 502 大面积出现了才开始到处翻日志定位。
常见问题
PHP-FPM 子进程满了,直接调大 pm.max_children 行不行?
可以作为临时止血手段,但不能当成完整的修复方案。调大上限之后会消耗更多内存,如果慢请求本身没处理,只是让更多请求同时卡住,最后故障不会消失,反而可能从 502 演变成机器内存 OOM 告警。
为什么 CPU 占用不高也会出现 502?
PHP 请求很多时候是在等数据库、Redis、文件 I/O 或者第三方接口返回数据。等待的这段时间 CPU 看起来空闲,但对应的 worker 子进程并没有释放资源,新的请求照样没法拿到可用子进程执行。
PHP-FPM slowlog 打开之后会不会影响性能?
正常配置阈值下性能损耗非常小。建议只记录执行时长超过 2 到 5 秒的请求,优先用于故障定位;长期开启慢日志的话要配套做好日志轮转配置,避免磁盘空间被日志文件占满。
502 和 504 怎么区分?
502 更多出现在后端连接失败或者返回异常响应的场景,504 代表网关等待后端返回超时。实际排查的时候不要只盯着状态码区分问题,核心还是要把 Nginx upstream 错误、FPM 状态数据和慢日志内容对应上。
小结
PHP-FPM 子进程打满导致的 502,本质是“流量入口还在不断接请求,但后端可用的 worker 子进程已经没有余量了”。稳妥的处理方式是先用 Nginx 日志和 FPM 状态页确认是进程池拥堵,再从慢日志里找到长时间占用 worker 的异常请求。pm.max_children 可以帮你快速争取到故障恢复的缓冲时间,但慢查询优化、外部调用等待处理、限流规则配置和提前告警机制才是决定下次不会被同类问题打穿的核心。
-
文章 · php教程 | 1天前 | 日志 · HTTP · 超时控制 · php教程 · 接口监控 · 请求超时 stream_context_create PHP教程 PHP HTTP请求 接口探测器289 收藏
-
文章 · php教程 | 2天前 | 文件上传 · 后端开发 · php教程 · 安全校验 · move_uploaded_file $_FILES finfo_file PHP上传文件 文件安全校验409 收藏
-
164 收藏
-
文章 · php教程 | 3天前 | 文件上传 · php教程 · $_FILES · 上传安全 · MIME · PHP文件上传 move_uploaded_file $_FILES 文件校验 MIME PHP教程242 收藏
-
134 收藏
-
文章 · php教程 | 1星期前 | Redis · 迁移 · session · php教程 · 登录态 · redis session phpredis PHP教程 session.save_handler 分布式登录 回归检查145 收藏
-
199 收藏
-
232 收藏
-
336 收藏
-
178 收藏
-
471 收藏
-
240 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习