先给结论:两层限流配合使用
防 CC 攻击,单靠一个指令不够。正确做法是:用 limit_conn 限制单个 IP 同时打开的连接数,用 limit_req 限制单个 IP 单位时间内的请求数(漏桶算法,允许突发)。两者都基于共享内存 zone,配置在 http 块里定义、在 server 或 location 里生效。触发限制时默认返回 503,建议改成 429 或 444,避免暴露 Nginx 特征。
前提:Nginx 需编译了 ngx_http_limit_conn_module 和 ngx_http_limit_req_module,官方包默认都带。用 nginx -V 2>&1 | grep -o with-http_limit 可确认。
第一步:定义共享内存 zone
在 http 块里加两行。zone 大小按需给,10m 大约能存 16 万个 IP 状态,一般站点 10m~20m 足够:
http {
# 并发连接:每个 IP 最多 20 个连接,共享内存 10m
limit_conn_zone $binary_remote_addr zone=conn_perip:10m;
# 请求速率:每个 IP 每秒 10 个请求,共享内存 10m
limit_req_zone $binary_remote_addr zone=req_perip:10m rate=10r/s;
}
注意用 $binary_remote_addr 而不是 $remote_addr,前者是 4 字节二进制,省内存;如果站点在 CDN 或反代后面,要先把真实 IP 还原到 $binary_remote_addr,否则所有请求都算到代理 IP 头上。
第二步:在 server / location 里启用
一般对动态接口(登录、搜索、提交)收紧,对静态资源放宽或干脆不限制:
server {
listen 80;
server_name example.com;
location / {
limit_conn conn_perip 20;
limit_req zone=req_perip burst=20 nodelay;
limit_req_status 429;
limit_conn_status 429;
proxy_pass http://127.0.0.1:8080;
}
location /api/login {
limit_conn conn_perip 5;
limit_req zone=req_perip burst=5 nodelay;
limit_req_status 429;
proxy_pass http://127.0.0.1:8080;
}
}
参数含义:burst=20 是允许的突发排队数,超过 rate 的请求先放进桶里;nodelay 表示桶里的请求立即处理而不是排队延迟,适合防刷场景。不加 nodelay 会人为拖慢响应,正常用户体验变差。
第三步:验证与调参
改完先 nginx -t 再 nginx -s reload。用 ab 或 wrk 压一下单 IP:
# 50 并发、共 200 个请求打登录接口
ab -n 200 -c 50 http://example.com/api/login
观察返回里 429 的比例,以及 error.log 里的 limiting requests / limiting connections 记录。调参原则:
- rate 设成正常用户峰值的 2~3 倍,太低会误伤;
- burst 不要给太大,否则攻击者仍能瞬间打出流量;
- 登录、短信验证码这类接口单独再收紧,配合应用层验证码更稳;
- 确认
limit_req_status和limit_conn_status都设了,默认 503 容易被扫描器识别。
几个容易踩的坑
第一,limit_conn 统计的是连接数不是请求数,HTTP/2 下单个连接可复用多个请求,限连接对 HTTP/2 效果弱,要靠 limit_req 兜底。第二,zone 定义必须放在 http 块,放 server 里会报错。第三,CDN 场景务必配置 set_real_ip_from 和 real_ip_header,否则限流对象错了。第四,这套方案是单机内存级防护,面对分布式 IP 池的 CC 效果有限,大规模攻击还需上 WAF 或云清洗。



