前几天深夜收到云监控报警,一台跑 WordPress 和几个自用小工具的 2G 内存轻量服务器 CPU 突然拉到 100%,进宝塔后台一看,系统负载飘红,十几个 php-fpm 进程把 CPU 核心吃得死死的,前台直接报 502 Bad Gateway。
类似的问题之前也踩过几次坑,通常要么是进程配置不合理导致系统频繁调度,要么是有恶意爬虫在刷动态接口,或者是某个 PHP 脚本陷入了死循环。这里把当时的排查步骤和调优参数整理一下,留个备忘。
1. 紧急处置与现场确认
CPU 飙到 100% 且站点已经打不开时,首要任务是让业务先恢复,再找原因。
先通过 SSH 连上服务器,用 top 命令看一眼资源占用情况:
bashtop -c
在进程列表中按键盘大写 P,可以看到按 CPU 占用排序的进程。如果看到一长串 php-fpm: pool www,且单进程占用都在 80%-100% 之间,基本可以确认是 PHP 处理逻辑卡住或者并发积压了。
此时直接在终端重载或重启 PHP 服务,先释放资源:
bash# 以 PHP 7.4 为例,执行重载 /etc/init.d/php-fpm-74 reload
如果 reload 之后几秒钟 CPU 又立刻飙满,说明正在有外部请求持续打进来,可以先暂时切断访问或者重启 Nginx 配合排查。
2. 关键参数设置与机器配置参考
宝塔安装完 PHP 后,默认的并发配置往往比较通用,小内存机器跑起来很容易爆。
在宝塔面板的【软件商店】->【已安装】->【PHP 设置】->【性能调整】中,重点调整这几个核心参数:
- pm(运行模式):推荐设为
dynamic(动态),让系统按需创建和回收进程,避免静态模式常驻占用过多内存。 - max_children(最大子进程数):这是最关键的阀值。单个 PHP-FPM 进程一般占用 20MB - 40MB 内存。如果设得太大,超出物理内存就会疯狂使用 Swap,进而导致 CPU 发生严重的上下文切换并飙升。
- request_terminate_timeout(脚本超时时间):默认可能是 0(不限制)或一个很大的数值。建议设置在
30到60秒之间,强制结束卡死超时的请求,防止僵尸进程常驻。
根据不同内存规格,我平时比较习惯使用下面这套相对稳妥的参数配置:
| 服务器配置 | max_children | start_servers | min_spare_servers | max_spare_servers | 建议 Swap 大小 |
|---|---|---|---|---|---|
| 1核 1G | 10 | 2 | 2 | 5 | 1024 MB |
| 2核 2G | 25 | 5 | 5 | 15 | 2048 MB |
| 2核 4G | 50 | 10 | 10 | 30 | 2048 MB |
3. 开启慢日志抓出具体卡死的文件
参数改好了,只是限制了并发上限,并不能从根本上解决“为什么个别脚本会吃满 CPU”。如果某个页面有慢查询或者死循环,单次请求就能把一个 CPU 核心占满 30 秒。
排查这类问题,最有效的方法就是开启 PHP 慢日志(Slow Log):
在宝塔对应 PHP 版本的【配置文件】(php-fpm.conf)中,找到以下两行并取消注释(或修改数值):
inirequest_slowlog_timeout = 3 slowlog = /www/server/php/74/var/log/slow.log
这里的意思是:只要执行时间超过 3 秒的请求,都会把完整的 PHP 堆栈调用栈记录到 slow.log 中。修改后平滑重启 PHP。
等再次出现卡顿或运行一段时间后,查看该日志:
bashtail -n 50 /www/server/php/74/var/log/slow.log
日志里会明确写出是哪个网站的哪一行代码执行超时。常见的元凶通常有两类:
- 远程 API 调用超时:比如调用微信支付、海外第三方接口,网络卡住导致进程挂起。
- 复杂的无索引 SQL 查询:数据库被拖慢,导致 PHP 进程一直阻塞等待。
4. 为什么参数改完还是会偶尔爆满?
在实际运维中,经常有人反馈:“参数已经压得很低了,脚本也没问题,怎么还是会被打崩?”
这种情况绝大部分是由于恶意爬虫抓取或针对搜索接口的小规模恶意请求造成的。
在宝塔的 Nginx 日志中,看一眼最近的访问记录:
bashtail -f /www/wwwlogs/你的域名.log
如果发现大量的海外 IP 在狂刷 /?s=xxx 这类站内搜索,或者某个爬虫不遵守 robots.txt 每秒发起几十次动态请求,PHP 就会因为短时间内大量唤醒子进程而迅速耗尽算力。
对于这种情况,单靠改 PHP 已经不够用了,需要把防御前置到 Nginx 层:
- 在 Nginx 中对频繁请求的 IP 做限速(
limit_req模块)。 - 禁止未知的爬虫 User-Agent 访问动态页面。
- 如果平时不想频繁手工翻日志过滤 IP,也可以配合宝塔自带的 Nginx 防火墙插件或网站监控报表,查看 IP 访问频次并直接做封禁拦截。
把高频无效请求挡在 Nginx 外层之后,PHP-FPM 的负载自然就会恢复正常。