先测量再调参:Nginx 与 PHP-FPM 的排障顺序、缓冲与超时边界
Nginx 与 PHP-FPM 调优的起点是定位瓶颈在哪一层,而非套用参数。文章按连接、排队、执行、缓冲与超时四层给出排查顺序,说明 pm 三种模式的取舍与 max_children 的测算思路,并强调 Nginx 读超时、FPM 请求终止超时、PHP 脚本超时三者的触发顺序会改变日志与错误码。所有示例均为未执行示意,具体取值需结合官方文档与本地观测核实。
- 调参前先确认瓶颈在连接层、进程排队、应用执行还是缓冲超时,否则改任何参数都只是掩盖症状。
- Nginx 的 fastcgi_read_timeout、FPM 的 request_terminate_timeout
- 进程池规模的合理值来自可用内存与单进程常驻内存的实测比值,不是照搬他人的数字。
这类文章常被写成一串参数清单,但真正决定调优是否有效的,是你能不能先回答一个问题:慢在哪一层。Nginx 与 PHP-FPM 之间隔着 FastCGI 协议、两段缓冲、两套超时和一层进程池,任何一处配置都可能让另一处的症状变形。本文按排查顺序讲取舍,不给“通用最优值”——具体数值需要结合你的版本、硬件和流量实测,本文中的配置片段均为未执行的示例。
一、先把链路拆成四层#
一次 PHP 请求在 Nginx + PHP-FPM 架构里大致经过四段,每段的失败表现不同:
- 连接层:客户端到 Nginx,以及 Nginx 到 FPM 的 socket/TCP 连接。表现为连接被拒、握手超时。
- 排队层:请求进入 FPM 的监听队列,等待空闲 worker。表现为 listen queue 堆积、响应时间整体抬升。
- 执行层:PHP 脚本本身,含数据库、外部接口、文件 IO。表现为个别接口慢而其他接口正常。
- 缓冲与超时层:Nginx 缓冲上游响应、各方超时先后触发。表现为错误码与日志对不上。
顺序很重要:如果瓶颈在执行层,把进程池调大只会让更多 worker 一起等数据库,内存占用上升而吞吐不变。这也是“调参无效”的最常见原因。
二、进程池:三种模式的取舍#
PHP-FPM 的进程管理由 pm 决定,三种模式的语义差别比很多人以为的大:
| 模式 | 行为 | 适合的场景 | 需要留意的代价 |
|---|---|---|---|
static | 启动即固定数量的子进程,不伸缩 | 流量平稳、内存可预估 | 空闲期也占用全部内存 |
dynamic | 在最小/最大空闲进程区间内增减 | 有明显波峰波谷 | 扩容有延迟,突发流量下先堆积队列 |
ondemand | 有请求才启动子进程 | 低频、长尾服务 | 冷启动延迟落在请求上 |
三种模式都会受 pm.max_children 这一上限约束。合理值的推导思路是:可用内存 ÷ 单进程稳定态常驻内存,再扣除系统、Nginx、数据库客户端等占用,留出余量。这里的关键是“稳定态”——PHP 进程的内存峰值常出现在处理大数组或大文件时,用峰值算会过于保守,用空载值算会过于激进。单进程常驻内存必须实测,不同框架与扩展差异很大。
; 未执行示例:仅示意指令位置,数值均为占位,不是推荐值
[app]
pm = dynamic
pm.max_children = 32 ; 占位:需按可用内存与单进程内存测算
pm.start_servers = 8 ; 占位
pm.min_spare_servers = 4 ; 占位
pm.max_spare_servers = 12 ; 占位
pm.max_requests = 500 ; 占位:用于缓解长期运行的内存增长
pm.max_requests 的作用是让 worker 处理一定请求数后重启,用于对冲第三方扩展的内存泄漏。它本身有成本(重启期间的抖动),是否启用应看内存曲线是否真的单调上升,而不是默认打开。
三、缓冲:Nginx 替上游挡了多少#
fastcgi_buffering 打开时,Nginx 先把 FPM 的响应读进缓冲区再发给客户端,好处是慢客户端不会长期占住 PHP worker。关闭后响应直通,PHP worker 的占用时间会被客户端速度牵着走。多数场景应保持开启,但要理解它的副作用:缓冲区大小不合适时,大响应会写临时文件,磁盘 IO 反而成了新瓶颈。
# 未执行示例:仅示意指令关系,数值均为占位
location ~ \.php$ {
include fastcgi_params;
fastcgi_pass unix:/run/php-fpm/app.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_buffering on;
fastcgi_buffers 16 16k; # 占位
fastcgi_busy_buffers_size 32k; # 占位
fastcgi_connect_timeout 5s; # 占位
fastcgi_read_timeout 60s; # 占位:需与 FPM 侧协同
}
这些指令的默认值随 Nginx 版本可能不同,改动前建议以你所部署版本的官方文档为准,本文不对具体默认值作断言。
四、超时:谁先触发,决定你看到什么错误#
链路上至少有三个超时同时存在,它们互相掩盖:
- Nginx 侧
fastcgi_read_timeout:等待上游响应的读超时。 - FPM 侧
request_terminate_timeout:超时后 FPM 直接终止该次请求的执行。 - PHP 侧
max_execution_time:脚本自身的执行时间限制。
三者的相对大小会改变现象:如果 Nginx 的读超时最短,你看到的是网关超时类错误,而 PHP 侧可能仍在继续执行;如果 FPM 的终止超时最短,worker 被中断,Nginx 拿到的上游响应会异常。因此排查时应先确认哪一个先到点,再看日志里对应的时间戳,而不是看到报错就去调大某一个值。
经验规则:让内层超时略短于外层,使错误在离问题更近的地方暴露;但具体秒数取决于业务的最长合理耗时,需要从慢日志与访问日志的实际分布中取,而不是凭感觉设置。
五、观测手段:慢日志与状态页#
没有观测就先调参,等于在盲改。request_slowlog_timeout 配合 slowlog 可以在请求超过阈值时记录 PHP 调用栈,直接指向具体函数而不是“某个接口慢”。pm.status_path 暴露的状态页则能看活动进程数、空闲进程数与监听队列长度——队列持续非零,说明进程池是瓶颈;队列为零而响应仍慢,瓶颈多半在执行层。
; 未执行示例
[app]
request_slowlog_timeout = 3s ; 占位阈值
slowlog = /var/log/php-fpm/slow.log
这类日志会带来额外开销,是否长期开启应结合预发环境的观察结果决定。生产环境的采样策略与保留周期也需要一并规划。
六、调优之前先确认补丁状态#
性能与安全在同一台机器上不是两件事。KrebsOnSecurity 在 2026 年 10 月的报道中提到,近七成网站需要更新核心软件以修复一对可导致服务器被完全接管的 PHP 漏洞,且当时已有可用利用代码(来源,属二手报道转述)。Cloudflare 随后针对 CVE-2026-109618 与 CVE-2026-109619 开设了 PHP CTF 靶场,其环境说明中写明靶机运行 PHP 8.5.11 与 Apache 2(mod_php)(来源)。
这里有一个对调优有直接影响的细节:同一份 PHP 代码在 mod_php 与 PHP-FPM 下,进程模型、内存归属和超时行为都不同。因此照搬别人的 Apache 调优经验到 FPM 架构上,结论往往不成立。至于你的 PHP 版本是否存在上述漏洞,应以官方安全公告与包管理器的补丁记录为准——本文无法核实你环境的具体版本状态。
七、边界:哪些结论需要你自行验证#
- 参数默认值:不同 Nginx / PHP-FPM 版本存在差异,需查对应版本文档(待核实)。
- 单进程常驻内存与峰值内存:必须在本机测,本文未提供任何测量数据。
- 缓存与边缘层:若在源站前还有 CDN,缓存命中情况会改变源站压力分布。Cloudflare 的说明指出其私有源站方案支持缓存、WAF 与限流等能力(来源),但这属于另一层配置,不能替代源站进程池与缓冲的判断。
- 所有示例代码均未执行,也不构成对任何具体数值的推荐。
把顺序摆正:先确认补丁、再定位层级、然后加观测、最后才动参数。顺序错了,参数改得越多,越难回滚到已知状态。
常见问题(FAQ)
只调大 Nginx 的 worker 相关参数能消除 502 吗?
开启慢日志会明显拖慢服务吗?
pm.max_children 有通用推荐值吗?
参考来源
- Cloudflare services for private origins is now generally available
- Introducing the Cloudflare PHP Capture the Flag challenge
- Widespread PHP Flaw May Cause Many Site Outages Today
以上为本文写作时的参考资料,引用观点以原文为准。