Nginx 限流防刷是保护后端服务的第一道防线。当恶意爬虫、CC 攻击或突发流量来袭时,Nginx 的 limit_req / limit_conn 模块配合漏桶算法能精准拦住流量洪水,避免 PHP-FPM 被打垮。本文用可直接复制的配置,带你从原理走到生产落地。
一、为什么必须做限流:从一个 REST 雪崩说起
我们曾在一次连续请求后出现”公网站点 200、但 REST 接口整段 404″的诡异现象,根因正是本机 IP 触发了 Nginx 限频被封禁。这件事暴露了一个朴素道理:没有限流的服务,既挡不住外部攻击,也可能会在自身异常时放大故障。在部署限流前,建议先按服务器安全加固指南把 SSH、防火墙、Fail2ban 的底座打牢,限流是构建在其之上的”流量闸口”。
二、Nginx 限流的两把刀:limit_req 与 limit_conn
2.1 limit_req:基于漏桶算法的请求速率限制
漏桶算法的核心是”以恒定速率漏水”。无论上游请求多猛,下游处理速率被固定,超出的请求要么排队(burst),要么直接拒绝。它限制的是请求频率(QPS),最适合防刷接口、防爆破登录。
2.2 limit_conn:并发连接数限制
limit_conn 限制的是同一时刻的并发连接数,适合挡住”慢连接耗尽连接池”的攻击(如 Slowloris)。两者互补:一个控频率,一个控并发。
三、实战一:limit_req 基础限流配置
第一步在 http 块定义”限流区”:用客户端 IP($binary_remote_addr)做键,分配 10MB 共享内存,限制速率为每秒 10 个请求(rate=10r/s)。第二步在 server 或 location 中引用它:
# 在 http 块定义限流区
limit_req_zone $binary_remote_addr zone=perip:10m rate=10r/s;
server {
listen 443 ssl;
server_name fsdata.site;
# 对全站开启限流,突发最多 20 个请求排队
location / {
limit_req zone=perip burst=20 nodelay;
proxy_pass http://127.0.0.1:8080;
}
# 对 REST API 单独收紧:每秒 5 次,突发 10
location ~ ^/index\.php\?rest_route=/wp/v2/ {
limit_req zone=perip burst=10 nodelay;
limit_req_status 429; # 超限返回 429 而非默认 503
proxy_pass http://127.0.0.1:8080;
}
}
这里把 REST 路由单独收紧,正是吸取了此前 REST 被限频误伤的教训——对关键接口要更敏感地识别异常。
四、实战二:burst 与 nodelay 的取舍
burst 是”漏桶容量”,允许瞬间涌入的请求排队等待;nodelay 则让排队的请求立即处理而不延迟。两者组合决定了用户体验与防护强度的平衡:
# 宽松模式:burst=20 但无 nodelay,超出请求被延迟(平滑)
limit_req zone=perip burst=20;
# 激进模式:burst=20 nodelay,突发流量立即放行,之后再匀速
limit_req zone=perip burst=20 nodelay;
# 严格模式:burst=5 nodelay,几乎不给缓冲,适合纯 API
limit_req zone=perip burst=5 nodelay;
经验法则:面向人的页面用”无 nodelay”更平滑;面向机器的 API 用”小 burst + nodelay”更干脆。本站的Nginx 反向代理配置里同样可叠加这段限流,二者天然兼容。
五、实战三:limit_conn 限制并发连接
对下载、大文件、长轮询等场景,限频率不够,要限并发。下面限制单 IP 同时最多 10 个连接,并给整站设 100 的总上限:
# http 块定义连接限制区
limit_conn_zone $binary_remote_addr zone=perip_conn:10m;
server {
# 单 IP 并发连接上限
limit_conn perip_conn 10;
# 全站总并发连接上限
limit_conn server_conn 100;
limit_conn_status 429;
}
六、白名单与放行:别误伤自己
限流最怕误伤:健康检查、内部调用、CDN 回源都应放行。用 geo + map 声明白名单,再在 location 里条件跳过 limit_req:
# http 块:定义白名单
geo $limit_exempt {
default 1; # 默认受限
127.0.0.1 0; # 本机放行
10.0.0.0/8 0; # 内网放行
192.168.1.100 0; # 固定运维机放行
}
map $limit_exempt $limit_key {
0 ""; # 白名单:空键,不触发限流区
1 $binary_remote_addr;
}
limit_req_zone $limit_key zone=perip:10m rate=10r/s;
server {
location / {
limit_req zone=perip burst=20 nodelay;
proxy_pass http://127.0.0.1:8080;
}
}
七、限流 + WAF:宝塔防火墙的配合
Nginx 原生限流擅长”速率/并发”,但识别不了 SQL 注入、XSS 等语义攻击。两者要配合:Nginx 挡流量洪峰,WAF 挡恶意语义。在宝塔面板里开启「Nginx 防火墙 / 防 CC」时,务必把 /index.php?rest_route= 路径与服务器 IP 加入白名单——否则正如我们踩过的坑,本机调 REST 反而会被自己拦成 404。更完整的安全底座见服务器安全加固。
八、把限流命中接入监控
限流配完不能”配完就忘”。用 Nginx 的 limit_req_status 429 配合 log_subrequest 把超限请求记进访问日志,再用Prometheus + Grafana 监控面板采集 429 速率,一旦陡增就是攻击或代码异常的前兆:
# 在 http 块开启限流日志
log_format rate_limited '$remote_addr - $time_local - "$request" '
'status=$status req_limited=$limit_req_status';
access_log /var/log/nginx/access.log rate_limited;
# Grafana 中统计 429 占比的 PromQL 思路:
# sum(rate(nginx_http_requests_total{status="429"}[5m]))
# /
# sum(rate(nginx_http_requests_total[5m]))
九、生产避坑清单
| 坑点 | 现象 | 正确做法 |
|---|---|---|
| 键用错 | 用 $remote_addr 占用内存大、易被 NAT 共用 IP 误伤 | 用 $binary_remote_addr,省内存且区分度更高 |
| burst 过大 | 队列堆积,延迟飙升、内存吃紧 | 页面 20 左右,API 5–10,结合 nodelay 实测调 |
| 漏加白名单 | 健康检查/内网被限流 429 | 用 geo+map 放行本机与内网段 |
| 误伤 REST | 本机调 API 触发 WAF 限频整段 404 | 宝塔防火墙把 REST 路径与服务器 IP 加白 |
| 无监控 | 限流命中了也不知道 | 记 429 日志 + Prometheus 告警 |
十、总结
Nginx 限流防刷用 limit_req(漏桶算法控频率)与 limit_conn(控并发)两把刀,配合白名单与 WAF,构成了后端服务的流量护城河。记住三步落地法:先在 http 块定义 zone,再在 location 引用并调 burst/nodelay,最后用监控闭环。限流不是”越严越好”,而是在防护强度与正常用户体验之间找到那个甜点——而找到它的唯一方法,就是上线后盯着 429 曲线持续调参。




