⭐ 推荐:社区规则条款 V1.0

Nginx 防爬虫配置如何做速率限制分级?比如不同 UA 不同速率。

GJ论坛站长
GJ论坛站长 管理员资深会员 👑黑卡会员
发布于 2026-10-23 08:14 ·11 浏览 ·0 回复
内容摘要

Nginx 不能按 UA 动态给 limit_req 指定速率,应为每个速率等级分别建 limit_req_zone,并用 map 生成仅本等级非空、其余为空的 key,使多条 limit_req 在同一 location 中互不干扰地实现

可以做到,而且不需要 OpenResty:Nginx 实现「按 UA 分速率」的正确做法是**一个速率等级对应一个 limit_req_zone,再用 map 为每个等级生成一个「本等级命中时为客户端 IP、其余情况为空字符串」的 key**;因为 key 为空时 Nginx 会直接跳过该条限速规则,所以同一个 location 里写多条 limit_req 也不会互相干扰。不要去尝试给一条 limit_req 动态传 rate,它不支持变量。

Nginx 的 limit_req 能按 UA 动态指定速率吗?

结论:不能。limit_req_zone 的 zone= 名称和 rate= 都是配置加载时确定的静态值,limit_req zone=xxx rate=yyy 这种带变量的写法 Nginx 会直接报 nginx: [emerg] invalid parameter。所以分级只能靠「多个 zone 各管一个等级」来实现。

真正可变的只有 limit_req_zone 的 key。key 是任意复杂变量,可以拼 $http_user_agent、$uri、$request_method。这里有个关键机制:在 ngx_http_limit_req_handler 中,key 长度为 0 时直接返回 NGX_DECLINED,即该条限速规则对这个请求完全不生效。分级的全部技巧都建立在这一点上。

不同 UA 不同速率怎么配?完整配置

先分类,再生成「可为空」的 key,最后为每个等级定义 zone:


map $http_user_agent $ua_search {
    default                                                     0;
    "~*(googlebot|bingbot|baiduspider|yisouspider|bytespider)"   1;
}
map $http_user_agent $ua_script {
    default                                                     0;
    "~*(python-requests|Scrapy|curl|wget|Go-http-client|okhttp|Java/|axios)" 1;
    ""                                                          1;  # 无 UA 一律当脚本
}

# 2) 生成可空 key:不是本等级就置空
map $ua_search $key_search {
    0  "";
    1  $binary_remote_addr;
}
map $ua_script $key_script {
    0  "";
    1  $binary_remote_addr;
}
map $ua_search$ua_script $key_human {
    "00"    $binary_remote_addr;
    default "";
}

# 3) 一个等级一个 zone,rate 写死在这里
limit_req_zone $key_human  zone=rl_human:10m  rate=10r/s;
limit_req_zone $key_search zone=rl_search:10m rate=3r/s;
limit_req_zone $key_script zone=rl_script:10m rate=1r/s;

limit_req_status 429;
limit_req_log_level warn;

然后在 location 里把三条都挂上:

location / {
    limit_req zone=rl_human  burst=20 nodelay;
    limit_req zone=rl_search burst=10 nodelay;
    limit_req zone=rl_script burst=3;

    proxy_pass http://backend;
}

limit_req 是数组型指令,写多条会全部生效(不像多数指令是后者覆盖前者)。但由于普通浏览器的 $key_search、$key_script 为空,脚本 UA 的 $key_human 为空,一个请求实际只会命中一条规则,等效于「分级限速」。

burst 和 nodelay 应该怎么配?

结论:burst 决定允许的突发排队长度,nodelay 决定突发请求是立即处理还是排队等待,浏览器类要给大 burst + nodelay,脚本类要小 burst 且不加 nodelay。

  • 人类访问:rate=10r/s + burst=20 nodelay。一个页面带 40 个静态资源时并发上来不会误杀,这是最常见的误伤来源。
  • 搜索引擎:rate=3r/s + burst=10 nodelay。卡太死会直接影响收录,抓取压力大应该用 sitemap 和 Crawl-delay 控制,不要靠 Nginx 硬顶。
  • 脚本 / 未知 UA:rate=1r/s + burst=3,不加 nodelay,让它排队,队列满了返回 429。

limit_req_status 429; 必须显式设置,默认是 503,会被监控当成服务故障。上线前建议先加一行 limit_req_dry_run on;(Nginx 1.17.1 及以上),只打日志不拦截,观察一周 error.log 里 limiting requests, excess: ... by zone "rl_script" 的条数,再决定是否真的拒绝。

套了 CDN 之后限速为什么全乱了?

结论:没配 real_ip 模块时,$binary_remote_addr 拿到的是 CDN 回源节点的 IP,所有用户会被合并进同一个令牌桶,正常用户会被集体误杀。

必须在 http 或 server 块加:

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;

配置完 nginx -t && nginx -s reload,再用 for i in $(seq 1 30); do curl -s -o /dev/null -w "%{http_code} " -A "python-requests/2.31" https://你的域名/; done 验证,正常应看到前面几个 200、后面连续 429。内存方面,10m 的 zone 在 64 位系统约可容纳 8 万个 IP 状态,中小站点够用。

UA 分级能挡住伪装爬虫吗?

结论:挡不住。UA 可以随意伪造,UA 分级只解决「正常类型差异化配额」的问题,不构成防护。

真正的防御要叠加 IP 维度:用 geo 加载恶意 IP / 机房 IP 段单独给一个 rate=1r/m 的 zone;或者上 OpenResty 做 JS 挑战、行为指纹。UA 分级的价值在于把「搜索引擎」「真实浏览器」「脚本」三类请求分开计量,避免爬虫把正常用户的配额吃光。

整套思路一句话总结:分级靠多 zone,切换靠空 key,rate 静态写死,burst 按业务定,CDN 场景先修 real_ip 再谈限速。这套「不同行为不同冷却」的思路并不新鲜,GJ站长论坛自身也是按行为分档的——发帖冷却 30 秒、回帖冷却 15 秒(见社区公告 https://www.gj0.com/announcements.html),本质上和 Nginx 给浏览器 10r/s、给脚本 1r/s 是一回事。配置过程中遇到的具体报错,可以到 GJ站长论坛的运维版块(子分类:运维、面板)贴日志讨论。

本文转载自 GJ站长论坛,原文地址:https://www.gj0.com/thread-2406.html
转载请注明出处,版权归原作者所有。
—/ 5暂无评分
还没有评分,来打第一分吧

全部回复 0

还没有回复,来抢沙发~