本文约 3,083 字,预计阅读 8 分钟。文中命令与配置均经过实测,可直接参考执行。
引言:网站一有流量就 502,多半是 PHP-FPM 配置没算过
很多站点平时好好的,一做活动或者被爬虫扫一轮就开始 502、504。重启服务又好一会儿。去看日志,常见这么一句:
WARNING: [pool www] server reached pm.max_children setting (5), consider raising it
意思是:PHP 进程池满了,新请求排队,排到超时就被 Nginx 断掉。默认值(通常是 5)是按”能跑”设的,不是按”能撑流量”设的。
原理分析:PHP-FPM 进程池是怎么工作的
PHP-FPM 预先 fork 出一批 worker 进程,Nginx 通过 Unix socket 或 TCP 把请求交给空闲 worker。三种管理模式:
| 模式 | 行为 | 适用 |
|---|---|---|
static |
固定数量的 worker 常驻 | 内存充足、流量稳定,性能最好 |
dynamic |
在 min/max 之间按需扩缩 | 大多数站点的默认选择 |
ondemand |
有请求才 fork,空闲后回收 | 内存极小、访问稀疏的站点 |
ondemand 看似省内存,但每次 fork 有开销,突发流量下响应会变慢,WordPress 场景一般不推荐。
关键参数:
pm.max_children ; 同时最多多少个 PHP 进程
pm.start_servers ; 启动时的进程数
pm.min_spare_servers ; 最少空闲进程
pm.max_spare_servers ; 最多空闲进程
pm.max_requests ; 单个进程处理多少请求后重启(防内存泄漏)
保姆级实操
第一步:安装环境(Debian 12)
sudo apt update
sudo apt install -y nginx mariadb-server php8.2-fpm php8.2-mysql php8.2-curl \
php8.2-gd php8.2-mbstring php8.2-xml php8.2-zip php8.2-intl php8.2-imagick
Debian 12 官方源即为 PHP 8.2,无需加第三方源,这是它相比 Ubuntu 22.04 的一个便利之处。
第二步:测算单个 PHP 进程占用
先让站点跑起来,访问几个页面,然后看实际占用:
ps --no-headers -o "rss,cmd" -C php-fpm8.2 | awk '{ sum+=$1 } END { printf "平均占用: %.1f MB\n", sum/NR/1024 }'
假设输出 45 MB。注意要取访问过几个典型页面之后的值,刚启动时进程很轻,不具参考性。
第三步:计算 pm.max_children
公式:
max_children = 可用于 PHP 的内存 / 单进程平均占用
比如服务器 4GB 内存,系统 + Nginx + MySQL 约占 1.2GB,留给 PHP 约 2.5GB:
2560 MB / 45 MB ≈ 56
保守取 40~50。不要按峰值内存算,因为 WordPress 后台操作(备份、导入)会临时用更多内存。
第四步:写配置
/etc/php/8.2/fpm/pool.d/www.conf:
[www]
user = www-data
group = www-data
listen = /run/php/php8.2-fpm.sock
listen.owner = www-data
listen.group = www-data
pm = dynamic
pm.max_children = 45
pm.start_servers = 8
pm.min_spare_servers = 5
pm.max_spare_servers = 15
pm.max_requests = 500
request_terminate_timeout = 300s
slowlog = /var/log/php8.2-fpm.slow.log
request_slowlog_timeout = 5s
php_admin_value[memory_limit] = 256M
start_servers 一般设为 max_children 的 15%~20%,min/max_spare 覆盖日常波动区间。
第五步:开启慢日志定位问题页面
request_slowlog_timeout = 5s 会把执行超过 5 秒的请求记录下来,包括具体是哪个 PHP 文件的哪一行:
tail -f /var/log/php8.2-fpm.slow.log
典型输出:
script_filename = /var/www/mimangfei.com/index.php
[0x00007f...] mysql_query() /var/www/.../wp-includes/wp-db.php:1234
这种就是数据库慢查询,要去查插件或加索引。
第六步:同步调整 Nginx 与 PHP 超时
location ~ \.php$ {
fastcgi_read_timeout 300;
fastcgi_send_timeout 300;
fastcgi_connect_timeout 60;
}
Nginx 的超时必须 ≥ PHP 的 request_terminate_timeout,否则会出现”PHP 还在跑,Nginx 已经返回 504″的怪现象。
第七步:验证
sudo systemctl restart php8.2-fpm
sudo tail -f /var/log/php8.2-fpm.log # 不应再有 max_children 告警
简单压测(谨慎使用):
ab -n 200 -c 20 https://mimangfei.com/
常见踩坑与失败案例
- 把 max_children 设得很大导致 OOM —— 内存耗尽后系统会杀掉 MySQL,比 502 更惨。一定要留 30% 余量给系统和其他服务。
- 改了 www.conf 不生效 —— 必须
systemctl restart php8.2-fpm,reload 有时不重新读进程管理参数。 - sock 权限问题导致 502 ——
listen.owner/listen.group要与 Nginx 运行用户一致。 - 案例:某站点 2GB 内存却设了
pm.max_children = 100,流量高峰直接 OOM,MySQL 被 kill,数据库文件损坏。正确做法是降到 30 并加 Swap 缓冲。 - pm.max_requests 设 0 —— 表示永不重启进程,长期运行后内存泄漏会累积,建议设 500~1000。
总结
PHP-FPM 调优的核心就一句话:用实测内存反推进程数,而不是拍脑袋。流程是:先测单进程占用 → 按可用内存算 max_children → 配 dynamic 三件套 → 开慢日志观察 → 定期复盘。做完这一步,再配合页面缓存和 OPcache,一台 2 核 4G 的机器扛住日几千 IP 的 WordPress 站点是很轻松的。
