后端服务最怕什么?不是代码有 bug,而是流量瞬间被打满。爬虫、恶意刷接口、CC 攻击、甚至自家前端写了个死循环,都能让一台健康的服务器在几秒内 CPU 飙满、响应超时。Nginx 作为流量入口,是实施限流与防刷的第一道防线。这篇文章不讲理论空话,直接给出可落地的配置方案。
为什么限流要放在 Nginx 层
在应用层做限流当然可以,但有两个问题:一是请求已经到达应用,消耗了连接、线程和内存资源;二是每个服务都要重复实现限流逻辑,维护成本高。Nginx 在反向代理层拦截,能在请求进入后端之前就决定放行还是拒绝,效率高、覆盖广、对业务代码零侵入。
Nginx 提供了两个核心限流模块:
- ngx_http_limit_req_module:基于漏桶算法限制请求速率
- ngx_http_limit_conn_module:限制并发连接数
配合 map、geo、if 等指令,可以组合出相当灵活的防刷策略。
基础限流:限制请求速率
定义限流区域
在 http 块中定义共享内存区域:
http {
# 以客户端 IP 为 key,分配 10MB 共享内存,每秒允许 10 个请求
limit_req_zone $binary_remote_addr zone=req_per_ip:10m rate=10r/s;
# 以服务器名称为 key,限制单虚拟主机的总速率
limit_req_zone $server_name zone=req_per_server:10m rate=1000r/s;
}
$binary_remote_addr 比 $remote_addr 更省内存(4 字节 vs 7-15 字节),10MB 大约能存 16 万个 IP 的状态。rate=10r/s 表示每个 IP 每秒允许 10 个请求,Nginx 实际按毫秒精度处理,即每 100ms 放行一个。
在 location 中启用限流
location /api/ {
limit_req zone=req_per_ip burst=20 nodelay;
limit_req zone=req_per_server burst=200;
limit_req_status 429;
proxy_pass http://backend;
}
这里有几个关键参数需要理解:
- burst:允许的突发请求数。漏桶算法中,超出速率的请求会排队等待,
burst就是队列长度。burst=20意味着瞬间最多有 20 个请求排队。 - nodelay:排队请求立即转发,不延迟处理。如果不加这个参数,超出的请求会按速率逐个放行,客户端会感受到明显延迟。
- limit_req_status:被限流时返回的状态码,默认是 503,建议改为 429(Too Many Requests),语义更准确。
不同接口用不同策略
登录、短信验证码、支付等敏感接口需要更严格的限流:
limit_req_zone $binary_remote_addr zone=strict:10m rate=1r/s;
location /api/sms/send {
limit_req zone=strict burst=2 nodelay;
limit_req_status 429;
proxy_pass http://backend;
}
每秒 1 次、突发 2 次,基本能挡住短信轰炸类攻击。
并发连接限制
请求速率限制的是"每秒多少个请求",但有些慢接口(如文件上传、报表导出)单个请求就占用大量资源,需要限制并发连接数:
http {
limit_conn_zone $binary_remote_addr zone=conn_per_ip:10m;
limit_conn_zone $server_name zone=conn_per_server:10m;
server {
location /api/upload {
limit_conn conn_per_ip 5;
limit_conn conn_per_server 200;
limit_conn_status 429;
proxy_pass http://backend;
}
}
}
每个 IP 最多 5 个并发连接,整个虚拟主机最多 200 个。超过限制的请求直接返回 429。
白名单:别把自己人限了
限流最尴尬的情况是把自己的监控、CDN 回源或内部调用给限了。用 geo 模块排除可信 IP:
geo $limited {
default 1;
10.0.0.0/8 0;
172.16.0.0/12 0;
192.168.0.0/16 0;
203.0.113.50 0; # 公司出口 IP
}
map $limited $limit_key {
0 "";
1 $binary_remote_addr;
}
limit_req_zone $limit_key zone=req_per_ip:10m rate=10r/s;
当 $limit_key 为空字符串时,Nginx 不会对该请求做限流统计。这种写法比在 location 里写一堆 allow/deny 更清晰,也更容易维护。
防刷进阶:识别异常流量
拦截空 User-Agent 和异常 Referer
大量爬虫和扫描器不会设置 User-Agent:
map $http_user_agent $bad_ua {
default 0;
"" 1;
"~*curl" 1;
"~*python" 1;
"~*scrapy" 1;
"~*httpclient" 1;
}
server {
if ($bad_ua) {
return 403;
}
}
注意 if 在 server 块中要谨慎使用,这里只做 return 是安全的。
限制请求方法
大部分 API 只需要 GET 和 POST:
if ($request_method !~ ^(GET|POST|HEAD)$) {
return 405;
}
防御慢速攻击
Slowloris 类攻击通过保持大量慢速连接耗尽服务器资源:
server {
client_body_timeout 10s;
client_header_timeout 10s;
send_timeout 10s;
keepalive_timeout 15s;
client_max_body_size 10m;
client_body_buffer_size 128k;
}
这些超时参数能有效缩短恶意连接的存活时间。
日志与监控:限流不能是黑盒
被限流的请求需要记录,否则无法判断是误伤还是攻击:
log_format limit_log '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'limit_req=$limit_req_status';
access_log /var/log/nginx/access.log limit_log;
配合 limit_req_log_level warn; 可以在 Nginx error log 中看到限流触发记录。生产环境建议将 429 响应单独收集,接入监控告警,观察限流触发频率的变化趋势。
一套完整的配置示例
把上面的方案整合起来:
http {
# 限流区域定义
limit_req_zone $binary_remote_addr zone=general:10m rate=20r/s;
limit_req_zone $binary_remote_addr zone=strict:10m rate=1r/s;
limit_conn_zone $binary_remote_addr zone=conn_per_ip:10m;
# 白名单
geo $trusted {
default 0;
10.0.0.0/8 1;
192.168.0.0/16 1;
}
map $trusted $limit_key {
0 $binary_remote_addr;
1 "";
}
server {
listen 80;
server_name api.example.com;
# 全局超时
client_body_timeout 10s;
client_header_timeout 10s;
send_timeout 10s;
# 通用接口
location /api/ {
limit_req zone=general burst=40 nodelay;
limit_req_status 429;
proxy_pass http://backend;
}
# 敏感接口
location /api/sms/ {
limit_req zone=strict burst=2 nodelay;
limit_req_status 429;
proxy_pass http://backend;
}
# 上传接口
location /api/upload {
limit_conn conn_per_ip 5;
limit_conn_status 429;
client_max_body_size 20m;
proxy_pass http://backend;
}
}
}
几个容易踩的坑
共享内存不够用:limit_req_zone 的 10m 不是随便写的。如果 key 是 IP,每个状态约 64 字节,10m 大约存 16 万个。超出后 Nginx 会开始淘汰旧记录,限流效果下降。高流量场景建议加到 50m 甚至 100m。
rate 设得太低:前端一个页面可能同时发十几个 API 请求,如果 rate 设成 5r/s,正常用户都会被限。建议先观察线上 QPS 分布,再设定阈值。
忘记 nodelay:不加 nodelay 时,burst 内的请求会被延迟处理,用户会感觉接口变慢。除非你确实想平滑流量,否则都建议加上。
只限流不监控:限流阈值是拍脑袋定的,不监控触发频率就不知道是否合理。至少要观察一周的 429 比例,再调整参数。
限流不是一劳永逸的事,而是一个持续调优的过程。从宽松的策略开始,观察数据,逐步收紧,才能在保护后端和用户体验之间找到平衡点。
未经允许不得转载:任鹏个人博客 » Nginx 限流与防刷配置:保护后端服务的实用方案


朋友圈点赞图在线生成源码