目录
- 为什么要配置Nginx请求限流
- limit_req核心原理:漏桶算法
- 基础配置:limit_req_zone定义限流区域
- burst与nodelay:突发流量怎么处理
- 实战方案一:API接口限流
- 实战方案二:全站防刷与CC防护
- 实战方案三:按URI分级限流
- 自定义返回状态码和错误页面
- 限流日志级别与监控
- 常见坑与排查技巧
- 相关文章推荐
为什么要配置Nginx请求限流
网站上线后,迟早会遇到恶意刷接口、CC攻击、爬虫疯狂抓取的情况。不做限流,服务器资源会被瞬间耗尽,正常用户访问不了。Nginx的limit_req模块就是专门干这件事的——按规则限制请求速率,超出的直接拒绝或排队。
和连接数限制(limit_conn)不同,请求限流关注的是单位时间内的请求次数,更适合防护高频短连接的攻击方式。
limit_req核心原理:漏桶算法
Nginx请求限流用的是漏桶算法(Leaky Bucket)。简单说就是:请求像水往桶里倒,桶以固定速率往外漏水。桶满了,多余的水直接溢出(请求被拒绝)。
这个算法的好处:
- 平滑输出:不管进来多少请求,处理速率始终稳定
- 允许突发:burst参数就是桶的容量,短时间内多几个请求可以排队等待
- 超限拒绝:排队也满了,就返回503
比起令牌桶算法,漏桶更保守,不会突然放一大批请求出去,适合防护场景。
基础配置:limit_req_zone定义限流区域
限流的第一步是在http块里定义限流区域:
http {
# 按IP限流,每秒1个请求,分配10m共享内存
limit_req_zone $binary_remote_addr zone=perip:10m rate=1r/s;
# 按服务器名限流,每秒10个请求
limit_req_zone $server_name zone=perserver:10m rate=10r/s;
}
参数解释:
- $binary_remote_addr:用二进制格式的客户端IP作为限流key,比字符串格式省内存(1m内存大约能存16000个IP)
- zone=perip:10m:共享内存区域名称和大小,10m约记录16万个IP
- rate=1r/s:每秒允许1个请求,也可以写rate=30r/m(每分钟30个)
注意:limit_req_zone必须放在http块里,不能放在server或location里。
burst与nodelay:突发流量怎么处理
光设rate=1r/s太严格了——正常用户快速点击页面可能1秒发3个请求,第2、3个就会被拒绝。所以需要burst参数来放行突发请求:
location /api/ {
limit_req zone=perip burst=5;
}
burst=5的意思:允许排队5个超额请求。超出rate的请求不直接拒绝,而是排队等处理。但排队的请求会被延迟处理——每秒处理1个排队请求。
如果你不想让请求排队等待,加nodelay:
location /api/ {
limit_req zone=perip burst=5 nodelay;
}
nodelay的作用:排队中的请求立即处理,不延迟。桶里5个突发请求一口气放出去,然后恢复1r/s的速率。适合用户体验优先的场景。
三者区别一句话:
- 只设rate → 超限立即503
- rate+burst → 超限排队延迟处理
- rate+burst+nodelay → 超限立即处理但桶满才503
实战方案一:API接口限流
API接口最需要限流,防止被恶意调用刷数据:
http {
# API限流:每秒5个请求,允许突发10个
limit_req_zone $binary_remote_addr zone=api:10m rate=5r/s;
server {
location /api/v1/ {
limit_req zone=api burst=10 nodelay;
limit_req_status 429;
# 自定义429错误页面
error_page 429 /429.html;
}
location = /429.html {
internal;
return 429 '{"error":"rate_limit","message":"Too many requests"}';
add_header Content-Type application/json;
}
}
}
这里把限流状态码改成了429(Too Many Requests),比503更语义化。API返回JSON格式错误信息,前端也好处理。
实战方案二:全站防刷与CC防护
CC攻击的特征是大量IP同时高频访问,单个IP可能不明显,但总量很大。对付这种攻击需要多维度限流:
http {
# 单IP限流
limit_req_zone $binary_remote_addr zone=cc_perip:10m rate=2r/s;
# 单服务器限流
limit_req_zone $server_name zone=cc_perserver:20m rate=100r/s;
server {
# 双重限流:IP级别+服务器级别
limit_req zone=cc_perip burst=10 nodelay;
limit_req zone=cc_perserver burst=50;
limit_req_status 503;
limit_req_log_level warn;
}
}
两层限流同时生效:单IP超2r/s触发第一层,全站超100r/s触发第二层。CC攻击一般单IP频率不高但总量大,perserver限流刚好能挡住。
limit_req_log_level warn:被限流的请求记录为warn级别日志,方便监控。
实战方案三:按URI分级限流
不同接口需要不同限流策略——登录接口要严,搜索接口可以宽:
http {
limit_req_zone $binary_remote_addr zone=login:5m rate=1r/m;
limit_req_zone $binary_remote_addr zone=search:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=static:10m rate=50r/s;
server {
# 登录:每分钟1次,防暴力破解
location /login {
limit_req zone=login burst=3 nodelay;
}
# 搜索:每秒10次,允许突发
location /search {
limit_req zone=search burst=20 nodelay;
}
# 静态资源:宽松限流
location /static/ {
limit_req zone=static burst=100 nodelay;
}
}
}
登录接口用1r/m(每分钟1次),暴力破解基本不可能。burst=3 nodelay允许3次排队快速处理,但1分钟内第4次登录直接拒绝。
自定义返回状态码和错误页面
默认限流返回503,但你可以改成更合适的:
# 改成429
limit_req_status 429;
# 配合自定义错误页面
error_page 429 /rate_limit.html;
location = /rate_limit.html {
internal;
root /var/www/error_pages;
}
也可以根据限流类型返回不同页面:API返回JSON,普通页面返回HTML提示。
限流日志级别与监控
限流日志是排查问题和监控攻击的重要依据:
# 限流拒绝 → error级别,限流延迟 → warn级别
limit_req_log_level warn;
# 查看限流日志
grep "limiting requests" /var/log/nginx/error.log
日志会记录被限流的IP、zone名称和超出速率。定期统计这些数据可以识别攻击来源。
更高级的做法是用dry run模式(1.17.1+版本支持):
limit_req_dry_run on;
dry run模式下请求不会被拒绝,但日志照常记录限流事件。适合调试阶段,先观察限流效果再正式启用。
常见坑与排查技巧
坑1:限流区域内存不够
zone大小设太小,IP记录满了后新IP的限流失效。1m约存16000个二进制IP,10m够16万个。站点流量大就多分配。
坑2:反向代理后$remote_addr全是代理IP
Nginx做反向代理时,所有请求的IP都是上游代理的IP,限流等于对代理限流,后面的真实客户端全被误杀。解决方法:
# 用真实客户端IP
set_real_ip_from 10.0.0.0/8;
real_ip_header X-Forwarded-For;
limit_req_zone $binary_remote_addr zone=perip:10m rate=1r/s;
坑3:burst设太大等于没限流
burst=1000配合nodelay,等于允许瞬间1000个请求全部放行,限流形同虚设。burst一般设5-20就够了。
排查技巧:
- 先开dry_run观察,确认限流规则生效
- 检查error.log里的limiting requests记录
- 用ab或wrk压测验证限流效果
相关文章推荐
版权声明
本文仅代表个人观点。
本文系AI辅助作者原创,未经许可,转载请保留原文链接。

发表评论