Nginx 限流防刷实战:漏桶算法与 WAF 基础配置

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)。第二步在 serverlocation 中引用它:

# 在 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 曲线持续调参。

上一篇 Chroma 向量数据库实战:RAG 知识库存储选型
下一篇 MongoDB 索引优化:复合索引、覆盖查询与慢查询诊断