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

Nginx 防爬虫规则放在 http、server、location 哪个块更有效?

GJ论坛站长
GJ论坛站长 管理员资深会员 👑黑卡会员
发布于 2026-10-22 03:20 ·4 浏览 ·0 回复
内容摘要

Nginx 防爬虫宜分层配置:limit_req_zone、limit_conn_zone、map、geo 等定义型指令只能置于 http,全站基线拦截放 server,搜索、接口等高价值路径在 location 收紧,多层同时生效时取最严

结论:分层放——limit_req_zone、limit_conn_zone、map、geo 这类「定义型」指令只能写在 http 块;真正的拦截动作写在 server 块做全站兜底,再用 location 块对 /search、/api 这类高价值路径单独收紧。三层同时写会同时生效,取其中最严的一条。

为什么 limit_req_zone 必须写在 http 块?

因为 Nginx 语法就这么定的:limit_req_zone、limit_conn_zone、map、geo 属于只能在 http 上下文声明的指令,写进 server 或 location 会直接启动失败,报 "limit_req_zone" directive is not allowed here。

http {
    # 定义限流区,10m 共享内存约可记录 16 万个 IP 的限流状态(1MB ≈ 1.6 万)
    limit_req_zone $binary_remote_addr zone=perip:10m rate=5r/s;
    limit_conn_zone $binary_remote_addr zone=conn_perip:10m;

    # 用 map 定义恶意 UA 变量,比在 location 里堆 if 更安全高效
    map $http_user_agent $bad_bot {
        default 0;
        ~*(python-requests|scrapy|curl|wget|Go-http-client|HeadlessChrome) 1;
    }

    # geo 定义 IP 白名单,搜索引擎与自家监控 IP 放行
    geo $white_ip {
        default 0;
        10.0.0.0/8 1;
    }
}

这里有个关键点:map 和 geo 都是「只计算变量、不执行动作」,放在 http 块是零成本的,只有真正被引用时才参与计算,不存在全局拖慢性能的问题。

server 块和 location 块该怎么分工?

分工原则是:server 块管全站基线,location 块管重点区域。爬虫打站往往不是均匀访问,80% 的压力集中在搜索页、列表页、详情页和接口上,全站一个阈值要么误伤正常用户,要么放跑爬虫。

server 块建议给一个宽松基线:

server {
    listen 80;
    server_name www.gj0.com;

    limit_req zone=perip burst=20 nodelay;   # 全站基线:5r/s,突发 20
    limit_conn conn_perip 20;                # 单 IP 并发连接上限
    if ($bad_bot) { return 403; }            # UA 黑名单直接拒绝
}

location 块对易爬路径收紧,burst 压到 3~5,并加上返回 429 的兜底:

location ~ ^/(search|api/list|thread-) {
    limit_req zone=perip burst=3 nodelay;
    limit_conn conn_perip 3;
    limit_req_status 429;
    proxy_pass http://127.0.0.1:8080;
}

提醒一句:if 在 location 里是著名的坑(if is evil),条件里出现 proxy_pass 时行为容易失控,所以 UA 判定统一用 map 生成变量,再在 server 层 return 403,不要在 location 里写复杂 if。

三层同时配置会不会冲突?

不会冲突,会叠加。limit_req 和 limit_conn 在 Nginx 的 preaccess 阶段执行,同一个请求如果同时匹配 http 继承下来的规则、server 的规则和 location 的规则,每一条都会被逐一检查,任意一条超限就按各自的 limit_req_status 返回(默认 503)。

所以不用担心「写了三层只生效一层」,反而要注意别把阈值叠得太死——比如 server 层 rate=5r/s、location 层又挂一个同样 rate=5r/s 的 zone,实际效果等于双重计数,正常用户也会被打。正确做法是同一路径只用一个 zone,靠 burst 区分松紧。

怎么验证规则真的生效?

用 curl 打高频请求 + 看 error.log 两条路交叉验证,比猜靠谱。


for i in $(seq 1 20); do curl -s -o /dev/null -w "%{http_code}\n" https://www.gj0.com/search?q=test & done; wait

# 确认限流日志(默认在 error.log,级别 error)
grep "limiting requests" /var/log/nginx/error.log | tail -5

日志里出现 limiting requests, excess: 3.2 by zone "perip" 就说明规则命中了;如果一条都没有,多半是 $binary_remote_addr 前面有 CDN 或反向代理,取到的是回源 IP,需要配合 real_ip_header 和 set_real_ip_from 还原真实客户端 IP,否则全站会被当成同一个 IP 一起限流。

三个常见配置错误

第一,把 limit_req_zone 写进 server 块,Nginx 直接起不来;第二,用了 $remote_addr 而不是 $binary_remote_addr,后者是二进制压缩存储,同样内存能存的 IP 数量约为前者的一半以内;第三,只靠 robots.txt 和 UA 黑名单,这两样对伪造 UA 的采集器基本无效,真正兜底的还是基于 IP 的频率与并发限制。

具体到实际站点,建议先用 server 层基线跑 3~7 天,从 access.log 里统计真实用户的峰值 QPS,再决定 location 层的 burst 数值,不要一上来就压到 1r/s。这类调优细节在站内的运维版块(https://www.gj0.com/ )也有不少实战帖可以对照。

总结一下:定义放 http,基线和 UA 拦截放 server,重点路径收紧放 location,三层叠加生效、取最严;上线前务必确认真实客户端 IP 能取到,否则限流会误伤全站。

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

全部回复 0

还没有回复,来抢沙发~