PHP-FPM 进程数怎么调:pm.max_children 与内存换算、502 反复排查

    发布时间:2026-09-25 05:00 更新时间:2026-09-25 05:00 阅读量:0

    网站跑着跑着突然打不开,浏览器返回 502 Bad Gateway,过一会儿又自己好了,这种间歇性故障在中小站点里出现频率很高。多数情况下问题出在 PHP-FPM 的进程池被占满,新请求排不上队,Nginx 拿不到上游响应就直接给访客丢了个 502。这篇文章把 PHP-FPM 进程数怎么算、怎么调讲清楚,再给一套 502 反复出现时的排查顺序,照着做能少走不少弯路。

    先搞明白 pm 的三种模式和进程数上限

    PHP-FPM 的进程管理由 php-fpm.conf 或对应进程池文件(常见路径 /www/server/php/版本/etc/php-fpm.d/www.conf,宝塔环境在 /www/server/php/版本/etc/php-fpm.conf)里的 pm 指令控制。pm 有三个可选值:static 固定进程数,启动就拉起 pm.max_children 个进程,不增不减;dynamic 动态伸缩,空闲时保留 pm.start_servers 个,忙时最多涨到 pm.max_children,超过 pm.max_spare_servers 的空闲进程会被回收;ondemand 更省内存,有请求才拉起进程,空闲到 pm.process_idle_timeout 后退出。

    对建站场景来说,dynamic 是比较均衡的选择:平时占用不高,流量上来能顶住。但不管选哪种,真正决定「同时能处理多少个 PHP 请求」的都是 pm.max_children。这个值设小了,并发一高就排队超时;设大了,进程互相抢内存,轻则频繁读写 swap 变慢,重则触发系统 OOM Killer 把 MySQL 或 PHP 进程杀掉,表现同样是 502。所以调进程数的本质是「在内存允许范围内,让 max_children 尽量够用」。

    按可用内存反推 pm.max_children

    思路很直白:先算出系统能给 PHP-FPM 用多少内存,再除以单个 PHP 进程的平均占用,就是安全的上限。单个进程占多少不能靠拍脑袋,得实测。最省事的办法是看当前 FPM 进程的常驻内存,用下面这条命令粗略统计(路径按实际 PHP 版本调整):

    ps -ylC php-fpm --sort:rss | awk 'NR>1 {sum+=$8; n++} END {print "进程数:" n, "平均RSS(KB):" sum/n, "合计(MB):" sum/1024}'
    

    注意 RSS 列在不同系统上位置可能不一样,如果输出对不上,可以换成 ps aux | grep php-fpm 看 RSS 字段。跑几次取偏高一点的数值更保险。WordPress 装了较多插件、又开了 OPcache 的站点,单个进程常驻 60~120MB 都算正常,具体以你服务器的实测为准。

    然后用 free -m 看可用内存。假设机器 4GB 内存,系统加 MySQL 加 Nginx 等常驻服务吃掉约 1.5GB,留给 PHP-FPM 的按 2GB 算,单个进程按 80MB 估算,那么 max_children 大约就是 2048 ÷ 80 ≈ 25。为了留出波动余量,实际可以填 20 左右。这条公式不复杂,关键是别把「总内存」直接当分子,一定要扣掉数据库和其他服务的占用。

    改完进程池文件后要重载 FPM 才生效,重载只重启 worker 进程,不会中断正在处理的请求:

    /etc/init.d/php-fpm-74 reload
    

    或使用 systemctl(版本号以实际安装为准)

    systemctl reload php-fpm

    同时建议把 pm.max_requests 设成 500~1000,让进程处理一定请求数后自动退出重建,能缓解个别插件导致的内存缓慢增长。

    502 反复出现时的排查顺序

    先把日志翻出来,别急着改参数。Nginx 错误日志(一般在 /www/wwwlogs/站点名.error.log 或 /var/log/nginx/error.log)里如果有「connect() to unix:/tmp/php-cgi.sock failed」或「upstream timed out」,基本能确定是 FPM 侧的问题;如果日志里是「worker_connections are not enough」或磁盘写入失败,方向就完全不同了。FPM 自身的慢日志和错误日志也要看,把 request_slowlog_timeout 和 slowlog 打开,能抓到执行时间过长的脚本。

    确认是进程池打满后,常见原因有这么几类。一是 max_children 确实偏小,流量高峰时 FPM 的 listen queue 堆积,用 ss -lnt 或 netstat -anp | grep php 能看到队列长度,也可以给 FPM 状态页(pm.status_path)配一个内部访问入口,观察 active processes 是否长期贴着上限。二是单个请求太慢,把进程长时间占着,比如某个插件在每次请求里都去请求外部接口,或者数据库缺索引导致查询扫全表,这种情况加大 max_children 只是把问题往后推,得从慢日志里找出慢脚本先解决。三是内存不够触发了 OOM,用 dmesg | grep -i oom 看有没有相关记录,如果有,说明当前进程数已经超出机器承受范围,反而要往下调。

    还有一个容易被忽略的点:Nginx 与 FPM 之间的超时参数不匹配。Nginx 的 fastcgi_read_timeout 如果比 FPM 的 request_terminate_timeout 短,长任务会被 Nginx 提前掐断报 502。这两处参数要一起看,建议让 Nginx 侧略大于 PHP 侧,具体取值结合业务中真正耗时的操作来定。

    调整顺序上,建议先做内存换算得出一个安全上限,把 max_children 设到该上限的七八成,观察一到两天;再用状态页和日志确认是否还有排队;如果仍然打满,就该去优化慢查询和慢脚本,而不是无脑往上加数字。整个过程记得改前备份原配置文件,方便出问题时快速回滚。

    继续阅读

    📑 📅
    宝塔面板定时任务备份到对象存储:命令行工具安装、密钥权限与保留份数设置 2026-09-24
    域名下手机跳m站收录分散:自适应与跳转取舍 2026-09-24
    WordPress 开启 HTTPS 后台重定向循环:is_ssl 与反代头排查 2026-09-24
    宝塔面板Nginx伪静态改了不生效:规则文件位置与重载验证 2026-09-24
    WordPress数据库连不上但宝塔显示正常:主机名与socket排查 2026-09-24
    MySQL只允许本机连接后网站报错:bind-address与用户host授权取舍 2026-09-25
    WordPress文章ID与固定链接优化:伪静态改动后旧链接兼容 2026-09-25
    宝塔面板网站备份还原到另一台服务器:跨机迁移的完整流程 2026-09-25
    WordPress定时任务被wp-cron拖慢:改用系统crontab配置与验证 2026-09-25
    网站根目录被写入异常PHP文件:时间线、属主与日志反查上传入口 2026-09-25