Nginx作为最常用的反向代理服务器,除了做负载均衡和静态资源分发,还内置了一套轻量的限流能力,核心就是limit_req指令。它基于漏桶算法实现,可以按固定速率处理进入的请求,超出容量的部分直接拒绝或延迟处理。相比在应用层写限流代码,这种方式几乎不消耗业务服务器的性能,配置简单,生效迅速,特别适合挡住突发流量和恶意刷接口的行为。这篇文章详细拆解limit_req的配置方法、参数含义和常见坑点。

limit_req的工作原理:漏桶算法
要理解limit_req的行为,先要明白它背后采用的是漏桶算法(Leaky Bucket)。想象一个底部有小孔的桶,请求像水一样从上方流入桶中,桶以固定速率把水漏出去。如果进水速度超过出水速度,水会在桶里堆积,一旦桶满了,多余的水就会溢出被丢弃。映射到Nginx中,"出水速率"就是我们在配置中声明的rate,"桶的容量"由burst参数决定,溢出的请求会被返回503错误(或自定义状态码)。
这种算法的特点是输出速率恒定,不管外部流量如何抖动,转发给后端的请求节奏是平稳的。这和令牌桶算法有本质区别:令牌桶允许一定程度的突发流量通过,而漏桶会把突发请求"削平"或者直接拒绝。对于保护脆弱的后端服务来说,漏桶这种宁可拒绝也不放行的策略往往更安全。
需要注意的一点是,limit_req的速率控制对每个key独立生效。如果以客户端IP作为key,那么每个IP都有自己独立的一个"桶",互不影响。这意味着限流粒度完全取决于key的选取,后面会详细讨论。
核心配置指令详解
limit_req的使用分为两步:先用limit_req_zone定义一个限流区域,再用limit_req在具体的location中启用它。先看基础配置:
# 在http块中定义限流区域
limit_req_zone $binary_remote_addr zone=mylimit:10m rate=10r/s;
server {
listen 80;
server_name example.ipipp.com;
location /api/ {
# 启用限流,允许突发20个请求排队
limit_req zone=mylimit burst=20 nodelay;
}
}
这段配置中有几个关键参数需要逐一说明。limit_req_zone的第一个参数是限流的key,这里用的是$binary_remote_addr,表示客户端IP的二进制格式。之所以推荐用二进制格式而不是$remote_addr,是因为二进制形式只占4字节(IPv4),能大幅节省共享内存的空间,10m的zone大约可以记录16万个IP状态。
zone=mylimit:10m声明了一个名为mylimit的共享内存区域,大小10MB。所有worker进程共享这块内存来记录各key的请求状态,所以限流是全局生效的,不会因为Nginx开了多个worker而形同虚设。rate=10r/s定义速率为每秒10个请求,也可以写成rate=30r/m表示每分钟30个。
limit_req指令放在location中,zone=mylimit引用上面定义的区域。burst=20的含义是桶的容量为20,允许瞬时突发20个请求排队等待。如果不加burst,只要请求间隔小于100毫秒(即超过10r/s的速率),就会立刻被拒绝,这对正常用户不太友好,因为浏览器加载页面时经常并发请求多个资源。
burst与nodelay的关系:最容易搞混的部分
很多初学者配置了burst之后发现用户体验反而变差了,原因在于没理解burst的默认行为。默认情况下,burst中的请求会被延迟处理,也就是排队。假设rate是10r/s,burst是20,某用户瞬间发出30个请求,第一个立即处理,接下来的20个会按照每100毫秒放行一个的节奏处理,最后9个被拒绝。用户感知就是页面加载卡顿明显。
加上nodelay参数后,行为发生变化:burst容量内的请求会被立即处理,不排队,但桶的空位仍然按固定速率恢复。还是上面的例子,30个请求瞬发,前21个立即得到响应,后9个被拒绝。这既容忍了正常浏览器的并发突发,又严格限制了整体速率,是生产环境最常用的组合。
location /login/ {
# 登录接口限制更严格,每秒1个请求,突发不超过5个
limit_req zone=login_limit burst=5 nodelay;
# 被限流的请求返回自定义状态码,方便前端识别
limit_req_status 429;
}
# 日志中记录被限流的请求,便于排查
limit_req_log_level warn;
limit_req_status可以自定义拒绝时返回的状态码,默认是503,建议改成429(Too Many Requests),语义更准确,也方便监控系统和前端统一处理。limit_req_log_level控制被限流请求在错误日志中的记录级别,默认是error,调试阶段可以设为info观察限流是否命中。
多维度限流与进阶用法
按IP限流是最常见的方案,但在CDN或代理后面,$binary_remote_addr拿到的是代理IP,所有人共享一个桶,误伤会非常严重。这时应该基于真实的客户端IP来做key,通常是$http_x_forwarded_for的第一个IP段,或者使用realip模块处理后直接取$binary_remote_addr。
除了IP,还可以按其他变量限流,比如按用户身份。假设后端在响应头中返回了用户标识,或者请求头中携带token,可以配合map指令构造限流key,实现每个账号独立限流。多个limit_req还可以叠加使用,即同一个location中写多条limit_req指令,只有所有条件都通过请求才会被放行,任何一个zone超限都会拒绝,适合实现"全局兜底加细粒度控制"的双层策略。
# http块:按请求头中的租户ID限流
limit_req_zone $http_x_tenant_id zone=tenant_limit:10m rate=100r/s;
server {
location /api/ {
# 双层限流:单IP限10r/s,单租户限100r/s
limit_req zone=ip_limit burst=20 nodelay;
limit_req zone=tenant_limit burst=50 nodelay;
}
}
配置不生效的常见原因排查
实际部署中经常遇到"配了但没效果"的情况,可以按以下顺序排查。第一,确认limit_req_zone必须定义在http块中,放在server或location里会直接报配置错误。第二,检查修改后是否执行了nginx -s reload,以及reload是否成功,可以用nginx -t先验证配置语法。第三,注意zone名称必须完全一致,大小写敏感,mylimit和MyLimit是两个不同的zone。
还有一个隐蔽的坑是location的匹配顺序。limit_req只在它所在的location中生效,如果请求实际命中的是另一个location(比如被正则location优先匹配走了),配置自然不起作用。此外,internal重写后的location会重新执行limit_req检查,配置rewrite时要注意别让请求绕过限流规则。验证限流是否生效,可以用ab或wrk压测工具快速观察:
# 用ab发200个并发请求观察限流效果 ab -n 200 -c 50 http://127.0.0.1/api/test # 观察错误日志中被限流的记录 tail -f /var/log/nginx/error.log | grep "limiting requests"
日志中出现limiting requests, excess字样说明限流已经命中,excess后面的数字表示超出桶容量的程度。最后提醒一点,限流阈值不要拍脑袋定,建议结合业务的QPS监控数据设置,一般设置为正常峰值的1.5到2倍,再通过灰度观察逐步收紧,避免上线就误伤真实用户。
Nginx limit_req请求速率限制限流配置修改时间:2026-09-08 14:53:34