这几天维护一台 2G 内存的轻量云服务器,上面跑着两个 WordPress 站点。白天访问量稍微多一点,CPU 就会偶尔直接拉满到 100%,接着 Nginx 开始频繁报 502 Bad Gateway。
SSH 连上去用 top 一看,全是 php-fpm: pool www 进程在疯跑。顺手把这次的排查步骤和改完后运行稳定的参数整理成备忘,遇到类似情况的朋友也可以参考。
1. 现场表现与紧急处置
如果站点已经打不开,当务之急是先让业务恢复:
bash# 查看当前 PHP-FPM 进程数量与占用 ps -ef | grep php-fpm | wc -l # 紧急重载或重启 PHP 服务(以 PHP 7.4 为例) systemctl reload php-fpm-74 # 或者直接在宝塔面板后台“软件商店 - PHP 设置”中点击重启
重启通常能立刻释放 CPU,但如果根源没解决,几分钟或几小时后还会复发。
2. PHP-FPM 关键参数调整
宝塔默认安装 PHP 后的并发设置通常比较激进,特别是对于 1G/2G 这样的小内存机器,默认进程数很容易在瞬时流量进来时把物理内存和 CPU 榨干。
进入宝塔面板 -> 软件商店 -> 对应的 PHP 版本 -> 设置 -> 性能调整,建议切换为 ondemand(按需)或调小 dynamic(动态)的进程数。
不同内存配置下的参考值:
| 服务器配置 | 运行模式 | max_children | start_servers | min_spare_servers | max_spare_servers | 建议 Swap 大小 |
|---|---|---|---|---|---|---|
| 1核 1G | ondemand / dynamic | 10 | 2 | 2 | 5 | 1024 MB |
| 2核 2G | dynamic | 20 | 5 | 5 | 15 | 2048 MB |
| 2核 4G | dynamic | 40 | 10 | 10 | 30 | 2048 MB |
为什么这么设:
每个 PHP-FPM 进程在执行复杂程序(如带多个插件的 WordPress)时,平均会吃掉 30MB 到 60MB 内存。如果 2G 机器把 max_children 设成 50,遇到突发访问,仅 PHP 就能占满 2GB 内存,系统开始频繁换页甚至触发 OOM 杀进程,CPU 就会一直卡在上下文切换和高负载状态。
此外,还可以修改 max_requests(每个子进程处理多少请求后自动重启释放内存),建议设为 500 或 1024,能有效缓解第三方 PHP 扩展偶尔存在的内存泄漏问题。
3. 开启慢日志定位卡顿代码
光改并发参数只是治标,如果某个请求本身执行需要 10 秒,那么只要来几个访客,所有子进程依然会被占满。
在宝塔 PHP 设置的“配置修改”或 pool 配置文件中,开启慢查询日志:
inirequest_slowlog_timeout = 3s slowlog = /www/server/php/74/var/log/slow.log
保存并重载 PHP。当有脚本执行超过 3 秒时,查看日志内容:
bashtail -n 50 /www/server/php/74/var/log/slow.log
慢日志会直接打印出具体的 PHP 文件和行号。常见原因有:
- 某个插件在页面加载时同步请求了挂掉的外部 API(超时卡死)。
- 数据库查询缺少索引,产生全表扫描。
- 大量文件上传未做限制,直接由 PHP 脚本处理大图裁剪。
4. 为什么改完参数依然会被打崩?
如果在参数合理、慢日志没有明显死锁的情况下 CPU 依旧飙高,通常是受到了非正常流量的访问:
- 恶意爬虫狂刷动态页面:查看网站访问日志,很多时候是某些搜索引擎蜘蛛或扫描器在无休止抓取搜索页(如
/?s=xxx)或分类归档。动态查询会绕过常规静态缓存,直接由 PHP 计算。 - 针对后台的暴力破解:比如 WordPress 的
xmlrpc.php或wp-login.php频繁遭到 POST 攻击。
排查这类问题,可以直接通过 Nginx 访问日志过滤高频 IP:
bash# 统计访问量最高的前 10 个 IP awk '{print $1}' /www/wwwlogs/your-site.com.log | sort | uniq -c | sort -nr | head -n 10
如果是爬虫或者恶意请求,可以在 Nginx 配置中直接封禁 IP,或者对敏感路径限制访问频率。如果不想手动翻日志维护过滤规则,也可以借助宝塔自带的 Nginx 防火墙插件或网站监控报表工具,快速拦截恶意请求并限制单 IP 的请求频率,把流量挡在 Nginx 层,PHP-FPM 的负载自然就会降下来。