Nginx limit_req 防爬虫参数怎么调才不误封?
调 Nginx limit_req 防爬虫应按真实客户端 IP 限流,按页面请求量估算 rate,用 burst 与 delay 吸收并发,并为搜索引擎和监控留空 key 白名单;页面建议 5r/s、burst=20、delay=10,登录
调 limit_req 防爬虫不误封的核心就一句话:**先把限流 key 换成真实客户端 IP,再按「页面请求量」而不是「单请求」估算 rate,然后用 burst + delay 吸收页面并发,最后给搜索引擎和自家监控留空 key 白名单**。只照抄 rate=1r/s burst=5 这种配置,正常用户刷两下页面就会被 503。
limit_req 的三个参数分别管什么?
结论:rate 管平均速率,burst 管瞬时并发,nodelay / delay 管突发请求是立刻处理还是排队。
配置由 limit_req_zone 和 limit_req 两部分组成:
limit_req_zone $binary_remote_addr zone=perip:10m rate=5r/s;
server {
location / {
limit_req zone=perip burst=20 delay=10;
limit_req_status 429;
}
}
rate支持r/s和r/m两种单位,内部按毫秒粒度计算,rate=5r/s约等于每 200ms 放行一个请求。burst是允许超出的请求数,超出burst的请求直接按limit_req_status返回(默认 503)。zone大小决定能记多少 IP:1MB 约存 16000 个状态,10MB 约 16 万。用$binary_remote_addr(4 字节)而不是$remote_addr(字符串),内存占用差好几倍。- 结论:
limit_req_status 429比默认 503 更好用,能和后端真实故障区分开,也符合 HTTP 语义。
rate 和 burst 怎么定才不误封正常用户?
结论:限流是对「请求」计数,不是对「页面」计数,一个首屏常带 20~60 个请求,所以 rate 必须按请求量算,不能按访客数算。
按这个起点调,再根据日志微调:
| 场景 | rate | burst | 说明 |
|---|---|---|---|
| HTML 页面 | 5r/s | 20,delay=10 | 首屏资源多的站点可放宽到 10r/s |
| 登录/注册/搜索接口 | 1r/s | 5,不加 nodelay | 让请求排队变慢,而不是直接拒绝 |
| 静态资源 | 50r/s | 100,nodelay | 优先交给 CDN,源站不限流 |
| 开放 API | 10r/s | 20,nodelay | 按业务 SLA 定 |
估算方法:用 access log 统计单个 IP 在 10 秒窗口内的峰值请求数,取 P99 再留 2 倍余量作为 burst。例如 P99 是 8 个请求 /10 秒,burst 至少给 16。
nodelay 和 delay 怎么选?
结论:nodelay 防误封效果最好,但会给爬虫「无限速」的错觉;delay=N(Nginx 1.15.7 起支持)是折中方案,前 N 个突发请求立即处理,超出的按 rate 排队。
- 纯
nodelay:burst 内请求全部秒回,用户无感,但爬虫只要控制并发就能一直薅。 - 纯
delay(不写 nodelay):所有 burst 请求都被平滑,页面会明显变慢,容易误伤。 burst=20 delay=10:前 10 个突发立即处理,第 11~20 个排队,第 21 个起返回 429。对人友好,对爬虫有成本。
怎么放行搜索引擎爬虫和自家监控?
结论:用 map 构造空 key,空 key 的请求不会被限流计数——这是 nginx 官方支持的免限流写法。
map $http_user_agent $limit_key {
default $binary_remote_addr;
"" "";
~*(Googlebot|Bingbot|Baiduspider|YisouSpider) "";
~*(UptimeRobot|Zabbix) "";
}
limit_req_zone $limit_key zone=perip:10m rate=5r/s;
注意两点:UA 可以伪造,重要站点应对 Googlebot/Baiduspider 做反向 DNS 校验(nslookup 查 IP 反解域名是否在 googlebot.com、baidu.com 域下);IP 白名单用 geo $whitelist { default 0; 10.0.0.0/8 1; } 配合同样方式给空 key。
最经典的误封原因是什么?
结论:站点在 CDN 或 SLB 后面却没还原真实 IP,所有用户共用一个回源 IP 作为限流 key,几百人同时访问就被限死。
修法是配 real_ip 模块,并确保 set_real_ip_from 只信任你自己的代理段:
set_real_ip_from 10.0.0.0/8;
set_real_ip_from 172.16.0.0/12;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
real_ip 生效后 $binary_remote_addr 才是用户真实 IP。除此之外,另外三个高频误封原因是:location 里写了 limit_req 就不会继承上层的(不同 location 记得各自配)、zone 太小导致状态被 LRU 淘汰后重新计数、以及 SPA 单页应用切换路由时一次性打出几十个 XHR 请求。
怎么先观察不拦截地调参?
结论:开源版没有 limit_req_dry_run(该参数是 NGINX Plus 专有),用「高 burst + 日志统计」代替灰度。
做法是先把 limit_req_log_level notice 打开,把 burst 调到目标值的 5 倍跑 24 小时,然后统计 error log 中 limiting requests, excess: X by zone "perip" 的条数和对应 IP,按 P99 反推真实需要的 rate/burst,再收敛参数。压测可以用 wrk -t4 -c50 -d30s 模拟正常用户并发,验证首屏资源加载不被 429。
收尾一句:先修 real_ip、再按请求量定 rate、用 burst+delay 吸收首屏并发、给爬虫和监控留空 key,四件事做完,limit_req 才既拦得住爬虫又封不到人。
转载请注明出处,版权归原作者所有。