适用场景
当你的 Web 服务器遭遇 CC 攻击(Challenge Collapsar)时,攻击者通过大量高频 HTTP 请求耗尽服务器连接池和带宽资源,导致正常用户无法访问。本文介绍如何使用 Nginx 原生 limit_req 模块构建一套可落地的限频防御方案,适用于单机 Nginx 或 Nginx 前置反代架构。
前置条件
- 操作系统:CentOS 7+ / Ubuntu 20.04+ / Debian 11+
- Nginx 版本:≥ 1.18.0(需编译
--with-http_limit_req_module,主流发行版默认包含) - 权限:root 或 sudo 用户
- 已安装
ab(Apache Bench)或wrk用于压测验证
执行以下命令确认模块可用:
nginx -V 2>&1 | grep -o 'http_limit_req_module'
输出 http_limit_req_module 即表示模块已启用。
原理说明
limit_req 模块基于 漏桶算法(Leaky Bucket)实现请求限速。核心机制如下:
- 定义限速key:按客户端 IP(
$binary_remote_addr)或其他变量作为限速标识 - 设置速率:如
10r/s表示每秒最多允许 10 个请求通过 - 突发缓冲:
burst参数控制短时突发请求的缓冲队列大小 - 无延迟模式:
nodelay参数允许突发请求立即处理而不排队等待
当请求速率超过设定阈值时,Nginx 直接返回 503 Service Unavailable,在高频攻击场景下快速丢弃恶意请求,保护后端服务。
操作步骤
Step 1:定义限速区域
编辑 Nginx 主配置文件 /etc/nginx/nginx.conf,在 http 块内添加限速区域定义:
http {
# ... 其他配置 ...
# 限制每个IP每秒最多10个请求,缓冲区10MB
limit_req_zone $binary_remote_addr zone=cc_defense:10m rate=10r/s;
# 可选:按请求URI限速(针对特定接口)
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=5r/s;
}
参数说明:
$binary_remote_addr:使用二进制格式的客户端IP,比$remote_addr更节省内存zone=cc_defense:10m:分配 10MB 共享内存,约可存储 16 万个 IP 记录rate=10r/s:每秒允许 10 个请求(平均每 100ms 一个)
Step 2:在 Server/Location 中应用限速规则
编辑站点配置文件(如 /etc/nginx/conf.d/wafai.conf):
server {
listen 443 ssl http2;
server_name www.wafai.cn;
# SSL 证书配置略
# 全局限速(所有请求)
location / {
limit_req zone=cc_defense burst=20 nodelay;
limit_req_status 503;
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
# API 接口单独限速(更严格)
location /wp-json/ {
limit_req zone=api_limit burst=10 nodelay;
limit_req_status 429;
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
# 静态资源不限速
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2?)$ {
# 不设置 limit_req
proxy_pass http://127.0.0.1:8080;
}
}
关键参数:
burst=20:允许 20 个突发请求排队缓冲nodelay:突发请求立即处理,不按速率延迟limit_req_status 503:被限速时返回 503 状态码(默认也是 503)
Step 3:配置 IP 白名单(信任源跳过限速)
使用 geo 指令定义白名单,避免误限 CDN 回源 IP 或监控探针:
http {
# 定义信任IP列表
geo $limit_ip {
default 1; # 默认限速
127.0.0.1 0; # 本地不限速
10.0.0.0/8 0; # 内网不限速
# CDN回源IP
123.45.67.0/24 0;
}
# 用变量控制限速key:信任IP使用空值跳过限速
map $limit_ip $limit_key {
0 "";
1 $binary_remote_addr;
}
limit_req_zone $limit_key zone=cc_defense:10m rate=10r/s;
}
这样配置后,白名单 IP 不会触发限速规则。
Step 4:添加自定义错误页面
为 503 状态码配置友好提示页面,避免用户体验生硬:
server {
# ... 其他配置 ...
error_page 503 = @cc_blocked;
location @cc_blocked {
default_type application/json;
return 503 '{"code":503,"msg":"请求过于频繁,请稍后重试"}';
}
}
Step 5:测试并重载配置
# 语法检查
nginx -t
# 平滑重载
nginx -s reload
配置验证
使用 ab 工具模拟高频请求,验证限速是否生效:
# 发送100个请求,并发20
ab -n 100 -c 20 https://www.wafai.cn/
# 预期结果:
# Complete requests: 100
# Failed requests: 80 ← 超出速率的请求被拒绝
# Non-2xx responses: 80 ← 返回503状态码
正常请求(低频)应全部返回 200:
# 每秒1个请求,共10个
for i in $(seq 1 10); do
curl -s -o /dev/null -w "%{http_code}\n" https://www.wafai.cn/
sleep 1
done
# 预期输出:10行 200
查看 Nginx 错误日志确认限速记录:
tail -f /var/log/nginx/error.log | grep "limiting requests"
# 预期输出示例:
# limiting requests, excess: 20.000 by zone "cc_defense", client: 1.2.3.4 ...
常见问题
Q1:burst 和 nodelay 怎么选?
burst 控制突发队列大小。不设 nodelay 时,突发请求会按设定速率依次处理(排队等待);设置 nodelay 后,突发请求立即处理但仍计入限速配额。对于 CC 防御场景,推荐使用 nodelay,避免正常用户排队等待。
Q2:站点使用 CDN,所有请求IP都是CDN回源IP,怎么限速?
需从 X-Forwarded-For 或 X-Real-IP 头中提取真实客户端 IP。在 http 块中添加:
set_real_ip_from 123.45.67.0/24; # CDN回源IP段
real_ip_header X-Forwarded-For;
real_ip_recursive on;
这样 $binary_remote_addr 会自动替换为真实客户端 IP,限速即可按真实 IP 生效。
Q3:限速太严格导致正常用户被误杀怎么办?
调整策略:适当提高 rate 和 burst 值;对静态资源路径不设限速;将 503 改为 429 状态码更符合 HTTP 语义;考虑结合 limit_conn(并发连接数限制)使用,而非仅依赖请求速率。
总结
核心要点回顾:
limit_req_zone定义限速区域和速率,limit_req在具体 location 中应用burst + nodelay组合兼顾灵活性与防护力度geo + map实现白名单豁免,避免误限 CDN 和内网- CDN 环境必须配合
set_real_ip_from提取真实 IP - 配合
limit_conn(并发限制)和 WAF 规则可构建多层防御
延伸阅读:建议进一步了解 limit_conn 并发连接数限制、Nginx Lua 扩展动态限速、以及 Fail2ban 联动自动封禁恶意 IP 的方案,构建更完整的 CC 攻击防御体系。