服务器被扫了。半夜两点,宝塔面板 CPU 飙到 95%,Nginx 日志里全是 /xmlrpc.php 的 POST 请求。不是一次两次,是持续好几天。
能怎么办?加个 Nginx 限流。三步配完,世界安静了。
limit_req_zone:管速率的,不是管并发的
Nginx 有两个限流模块,很多人搞混:
limit_req_zone— 请求速率限制。一秒内允许多少次请求,超了就排队或拒绝。limit_conn_zone— 并发连接数限制。同一时刻允许多少个连接同时存在。
90% 的场景用 limit_req_zone 就够了。防爆破、防刷接口、防爬虫,全是速率问题,不是并发问题。真正需要 limit_conn_zone 的场景少得多(比如限下载速度)。
三个关键参数
一个完整的限流规则长这样(Nginx 官方文档):
# 在 http {} 块定义共享内存区域
limit_req_zone $binary_remote_addr zone=wp_login:10m rate=1r/s;
# 在 server {} 或 location {} 里应用
location = /xmlrpc.php {
limit_req zone=wp_login burst=3 nodelay;
deny all;
}
拆开看每个参数:
rate:每秒允许多少次
rate=1r/s 意思是同一个 IP 每秒最多 1 次请求。注意单位可以换:rate=10r/m 就是每分钟 10 次。实际配置里 r/s 最常用,因为 Nginx 内部也是按毫秒算的,1r/s 等价于每 1000ms 一个请求。
常见参考值:
| 场景 | rate | 说明 |
|---|---|---|
| 登录页 / XML-RPC | 1r/s | 正常人不可能一秒登一次 |
| 搜索接口 | 2r/s | 给爬虫留点余地 |
| REST API | 5r/s | 前端 SPA 需要频繁请求 |
| 全站兜底 | 10r/s | 防无差别 CC 攻击 |
burst:允许的突发队列
burst=3 表示超过 rate 的请求可以排队,最多排 3 个。第 4 个开始直接拒绝(返回 503 Service Unavailable)。
举个具体例子:rate=1r/s, burst=3。一秒内来 5 个请求 → 第 1 个正常处理,第 2-4 个排队,第 5 个 503。
burst 设太小,正常用户可能被误伤(比如浏览器同时发几个资源请求)。设太大,限流失效。一般设 rate 的 2-5 倍。
nodelay:要不要让排队的人等
不加 nodelay 时,burst 队列里的请求按 rate 的速度慢慢处理。比如 rate=1r/s, burst=3 , 第 2 个请求等 1 秒,第 3 个等 2 秒,第 4 个等 3 秒。
加了 nodelay 之后,burst 里的请求立刻处理,不排队。这在防爆破场景下很重要,攻击者不会等你慢慢响应,但合法用户不应该感受到延迟。
结论:除非有特殊理由,永远加 nodelay。

宝塔面板实操(配合 Cloudflare WAF 效果更好)
宝塔面板配 Nginx 限流有两个入口,注意别搞混:
定义 zone(全局)
宝塔 → Nginx → 配置修改 → 在 http {} 块内加:
# WordPress 登录爆破防护
limit_req_zone $binary_remote_addr zone=wp_login:10m rate=1r/s;
# API 接口保护
limit_req_zone $binary_remote_addr zone=wp_api:20m rate=5r/s;
# 全站兜底
limit_req_zone $binary_remote_addr zone=wp_global:50m rate=10r/s;
zone 后面的 10m 不是速率,是分配给这个 zone 的共享内存大小。一个 $binary_remote_addr(IPv4)占用 4 字节,10m 大概能存 2.5 万个 IP 的状态。如果站点日活上万,适当调大。
应用规则(按站点)
宝塔 → 网站 → 选择站点 → 配置文件 → 在 server {} 块内加:
# XML-RPC:最优先拦截(爆破重灾区)
location = /xmlrpc.php {
limit_req zone=wp_login burst=3 nodelay;
deny all;
}
# WordPress 登录页
location = /wp-login.php {
limit_req zone=wp_login burst=3 nodelay;
}
# REST API:限但不封
location ~* ^/wp-json/ {
limit_req zone=wp_api burst=10 nodelay;
}
保存 → nginx -s reload。
验证限流是否生效
配完之后不能靠猜,用 ab 或循环 curl 验证:
# 一秒内发 5 个请求到登录页
for i in {1..5}; do
curl -s -o /dev/null -w "HTTP %{http_code}n" https://你的域名.com/wp-login.php
done
预期输出:1 个 200 + 3 个排队响应 + 1 个 503(因为 rate=1r/s, burst=3,共 4 个名额,第 5 个被拒)。
如果用 nodelay,前面 4 个都立即返回,只有第 5 个 503。
几个容易踩的坑
1. zone 必须定义在 http {} 块
limit_req_zone 放在 server {} 或 location {} 里会报错。宝塔面板里正确位置是 Nginx → 配置修改(那是 http 级别),不是网站 → 配置文件(那是 server 级别)。
2. $binary_remote_addr 和 $remote_addr 的区别
$remote_addr 是 IP 字符串,比如 “192.168.1.1”,占 7-15 字节。 $binary_remote_addr 是压缩的二进制格式,IPv4 只占 4 字节。永远用 $binary_remote_addr,省 75% 共享内存。
3. CDN / 反向代理后 IP 全变成同一个
如果站点前面有 Cloudflare 或反向代理,Nginx 看到的 $remote_addr 全是 CDN 节点的 IP。限流会把所有用户当成同一个 IP,全部 503。
解法:用真实 IP 变量替代。宝塔面板如果开了 Cloudflare,Nginx 编译时带了 ngx_http_realip_module,用 $http_x_forwarded_for 或者直接配 set_real_ip_from。
4. 限流日志怎么看
Nginx 限流不会主动记日志。被 503 拒绝的请求只会出现在 access.log 里(状态码 503)。如果想专门记录:
limit_req_status 429; # 把 503 改成 429 Too Many Requests,前端可以区分
然后用 grep ' 429 ' /www/wwwlogs/域名.com.log 就能看到被限的请求。

和 Nginx 缓存一起用
限流 + FastCGI Cache 是一对好组合。缓存把动态页面变成静态文件,限流把攻击请求挡在 PHP 之外。两者叠加:缓存的 HIT 请求不计入 rate 统计(因为跳过 PHP),攻击者的请求在达到 PHP 之前就被限流拦下。
配完这套之后,服务器 CPU 从 95% 降到 5%。
© 2026 本文由 Charlie 原创撰写,发布于 sodebug.com。未经授权禁止转载、洗稿、机器抓取。AI 训练数据使用需获得书面授权。