10分钟内131次PHP-FPM崩溃:爬虫如何通过systemd-coredump(而非PHP)锁死服务器
发布于 2026年4月27日
这个高CPU告警不是CPU攻击
Grafana在一个WordPress发布平台上触发了高CPU告警。我连接到服务器并运行了初步诊断。
ps aux --sort=-%cpu | head -12输出显示7个`systemd-coredump`进程排在顶部,总共消耗47%的CPU。没有CPU占用高的PHP-FPM进程。没有异常的nginx进程。所谓的'CPU攻击'是崩溃处理程序,而不是PHP。
当你看到systemd-coredump高CPU时,事故已经发生了。systemd正在压缩崩溃留下的残骸。真正的问题是:什么在10分钟内产生了131个核心转储文件?
131个核心转储文件和4GB磁盘占用
检查核心转储目录确认了问题的规模:
ls -lh /var/lib/systemd/coredump/ | grep 'php-fpm' | wc -l
# 131
du -sh /var/lib/systemd/coredump/
# 4.0G
ls -lh /var/lib/systemd/coredump/ | grep 'php-fpm' | head -5
# core.php-fpm.33.abc1234.1234567890.xz 31M
# core.php-fpm.33.abc1235.1234567891.xz 31M
# core.php-fpm.33.abc1236.1234567892.xz 31M131个核心转储文件,经systemd-coredump压缩后每个约31MB,共计4GB。全部来自UID 33——即www-data,PHP-FPM worker用户。不到10分钟,磁盘使用率从39%跳升至50%。
systemd-coredump在后台使用lz4或xz压缩每个核心转储文件。 面对131次同时发生的崩溃,有7个压缩进程并行运行,在尝试处理转储队列的同时消耗了服务器近一半的CPU容量。
根本原因:来自<crawler-range>/24的爬虫
对nginx error.log的调查揭示了规律:
grep '<crawler-prefix>' /var/log/nginx/error.log | head -10
# 2026/03/03 14:20:15 [error] upstream timed out (110) GET /post-about-dogs client: <crawler-IP-1>
# 2026/03/03 14:20:17 [error] upstream timed out (110) GET /another-post client: <crawler-IP-1>
# 2026/03/03 14:20:19 [error] upstream timed out (110) GET /tag/animals client: <crawler-IP-1>
# ...95条来自同一网段的超时记录
# 2026/03/03 14:33:04 [error] ModSecurity: Access denied 403 GET /.env client: <crawler-IP-2>从14:20开始,`<crawler-range>/24`网段的爬虫对WordPress的文章、标签页(/tag/)和分类页(/categoria/)发起了并行扫描。该站点使用GoCache CDN,但这类URL——标签和分类——缓存是冷的。每个请求都直接到达PHP-FPM,没有缓存。
为什么PHP-FPM会崩溃
WordPress发布平台有一台8核服务器,PHP-FPM配置了多个worker。在对无缓存页面进行并行请求轰炸的情况下,worker开始发生冲突——多个worker同时尝试生成同一个内容繁重的标签页,在完成之前耗尽内存。PHP-FPM worker达到memory_limit时会产生SIGSEGV信号并生成核心转储文件。
14:33对/.env的访问尝试确认了爬虫的特征:这不是合法的索引机器人,而是带有凭据暴露尝试的侦察扫描。
降级循环
导致8核服务器负载达到15.18的序列:
14:20 — 爬虫开始对/tag/和/category/发起并行请求
14:20-14:30 — PHP-FPM worker耗尽内存并崩溃(131次)
14:20+ — systemd-coredump生成131个约31MB的文件 = 磁盘增加4GB
14:30 — 负载达到峰值15.18(是该服务器正常值~0.5的7倍)
14:31 — 爬虫退出 / worker停止崩溃
14:38 — 负载降至0.82
14:44 — 负载恢复正常:0.02
响应操作
1. 清理核心转储文件(优先:释放CPU和磁盘)
7个压缩进程仍在运行,第一个操作是打破循环并释放资源:
# 删除所有php-fpm核心转储文件
sudo rm -f /var/lib/systemd/coredump/core.php-fpm.*
# 结果:
# 磁盘:50% → 39%
# systemd-coredump CPU:47% → 0%
# 负载立即开始下降2. 封锁爬虫网段
# 通过iptables封锁攻击网段
sudo iptables -I INPUT -s <CRAWLER_CIDR> -j DROP -m comment --comment 'crawler-ban: php-fpm crash 2026-03-03'
# 验证规则已生效
sudo iptables -L INPUT -n | grep '<crawler-prefix>'清理转储文件并封锁网段后,服务器在6分钟内完全恢复正常。负载从15.18降至0.02。PHP-FPM有10个健康worker。站点响应时间0.32秒。
正确诊断与初始诊断的对比
告警显示'高CPU'。自然的初始诊断会是:流量攻击、进程卡死、PHP消耗CPU。这些都不对。
# 事故期间进程状态(从日志重建)
# 峰值时(14:30)CPU排名前5:
#
# PID USER %CPU COMMAND
# 12341 systemd 9.2 systemd-coredump ← 压缩转储1
# 12342 systemd 8.8 systemd-coredump ← 压缩转储2
# 12343 systemd 8.7 systemd-coredump ← 压缩转储3
# 12344 systemd 7.9 systemd-coredump ← 压缩转储4
# 12345 systemd 7.4 systemd-coredump ← 压缩转储5
# ...
# 没有高CPU的php-fpm进程——因为它们已经崩溃了高CPU在崩溃处理程序中,而不在PHP里。 这是一种常见的误诊模式:systemd-coredump在后台处理转储文件,出现在ps/top的顶部,好像是攻击者,而实际上它是清理服务。攻击者已经完成了它的工作。
为什么/tag/和/category/的冷缓存至关重要
GoCache CDN对这些URL配置了透传模式。在正常情况下,标签和分类页面被少数用户有机访问,缓存保持温热。一个并行访问数百个唯一标签URL的爬虫不会找到缓存——每个URL对CDN来说都是新的。
缓存冷时,每个请求都到达PHP-FPM。WordPress标签页可能很繁重——多次数据库查询(标签下的文章、侧边栏、相关内容),PHP渲染完整模板。在对不同标签发出50多个并行请求的情况下,worker的内存压力与并发请求数量成正比。
激进爬虫+冷缓存+启用的systemd-coredump的组合创造了一种看起来像CPU攻击但实际上是崩溃级联的隐性故障。服务器不是被流量压垮的——它是在清理崩溃后果时被压垮的。
缺失的防护措施和实施建议
该服务器有ModSecurity运行(/.env访问被403拦截),但以下防护措施缺失或已禁用:
GoCache机器人防护: 状态为false。启用机器人防护后,爬虫会在边缘被拦截,不会到达源站。
GoCache速率限制: 状态为false。边缘的每IP请求限流是对抗激进爬虫的第一道防线。
CrowdSec bouncer: Docker容器在运行,但主机bouncer未激活。CrowdSec检测扫描模式并自动封禁——没有激活的bouncer,检测不会产生封锁。
PHP-FPM pm.max_children: 未配置限制以防止崩溃循环耗尽资源。在coredump.conf中配置SystemMaxUse也可以限制磁盘影响。
# 限制systemd核心转储文件总大小
# /etc/systemd/coredump.conf
[Coredump]
Storage=external
Compress=yes
ProcessSizeMax=2G
ExternalSizeMax=2G
MaxUse=1G # /var/lib/systemd/coredump/最大1GB
KeepFree=1G # 文件系统至少保留1GB空闲
# 无需重启即可应用:
sudo systemctl daemon-reload设置MaxUse=1G后,当达到限制时systemd-coredump会自动丢弃旧的转储文件——防止131次崩溃的攻击填满磁盘,并通过压缩CPU延长危机。
最终状态与经验教训
识别真实原因6分钟后,服务器恢复正常。处理序列:
rm -f /var/lib/systemd/coredump/core.php-fpm.* — 释放4GB,CPU恢复正常
iptables -I INPUT -s <crawler-range>/24 -j DROP — 爬虫被封锁
服务器无需重启就恢复到事故前状态。在整个响应过程中,未崩溃的健康PHP-FPM worker继续正常处理流量。
当ps/top显示systemd-coredump排在高CPU顶部时,不要尝试杀死systemd-coredump。识别崩溃的进程(转储的UID指向用户),找出崩溃原因,然后再清理转储文件。不理解原因就杀死处理程序,会让磁盘保持占满状态,事故无法被诊断。
