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

Nginx 的 geo 模块按国家/地区屏蔽爬虫的配置方法。

GJ论坛站长
GJ论坛站长 管理员资深会员 👑黑卡会员
发布于 2026-10-21 02:54 ·9 浏览 ·0 回复
内容摘要

Nginx的geo模块仅按IP段匹配,不能直接识别国家;按国家屏蔽爬虫可选geoip2模块配合GeoLite2数据库,或不装模块将国家CIDR列表灌入geo块,再用if+return 444拦截,后者零依赖但精度依赖列表,前者更推荐。

Nginx 的 geo 模块本身不认国家——它只按 IP 段匹配变量。想按国家/地区屏蔽爬虫,有两条路:装 ngx_http_geoip2_module + GeoLite2 数据库,用 $geoip2_country_code 判断;或者不装模块,直接把某个国家的 CIDR 列表灌进 geo 块。两者最后都靠 if + return 444 拦截,配置 10 行以内。

geo 模块能直接根据 IP 判断国家吗?

不能。geo 指令的语法是 geo [$address] $variable { ... },它做的是 IP 段到值的映射表,例如 1.0.1.0/24 1;。它没有任何地理数据库,无法从 IP 推导出国家码。想得到 CN、RU 这样的国家码,必须额外引入数据源:要么是 MaxMind 的 GeoLite2 .mmdb 数据库(走 geoip2 模块),要么是别人已经按国家切好的 CIDR 列表(走 geo 模块本身)。

结论:geo 是"匹配器",geoip2 才是"识别器",两者常配合使用,但不可互相替代。

另外提醒一句:老的 ngx_http_geoip_module 依赖 GeoIP Legacy 格式(GeoIP.dat),MaxMind 在 2019 年 1 月就停止更新该数据,现在新部署一律用 GeoIP2。

不装任何模块,用 geo + IP 段列表怎么做?

可以,适合不想重编译 Nginx 的场景。思路是从 ipdeny.com 拉取按国家聚合的 CIDR 文件,转成 geo 能读的格式。

第一步,生成列表文件(以屏蔽俄罗斯为例):

mkdir -p /etc/nginx/geo
curl -s https://www.ipdeny.com/ipblocks/data/aggregated/ru-aggregated.zone \
  | sed 's/$/ 1;/' > /etc/nginx/geo/ru.zone

第二步,在 http 块里 include 进 geo:

geo $block_ip {
    default 0;
    include /etc/nginx/geo/ru.zone;
}

server {
    listen 80;
    server_name example.com;
    if ($block_ip) { return 444; }
}

第三步,nginx -t && nginx -s reload。ipdeny 的聚合文件每天更新,用 cron 每周重拉一次并 reload 即可。这套方案的优点是零依赖、命中靠 radix tree 查找(O(log n)),几十万条 CIDR 也不影响性能;缺点是精度完全取决于列表质量,且不区分运营商。

用 GeoIP2 模块的完整配置怎么写?

推荐这条路线,精度和可维护性都更好。先装依赖并拿到数据库:注册 MaxMind 账号获取 license key,用 geoipupdate 拉取 GeoLite2-Country.mmdb;Debian/Ubuntu 上直接 apt install libnginx-mod-http-geoip2,源码编译则加 --add-dynamic-module=../ngx_http_geoip2_module。

配置分三层,全部写在 http 块:

geoip2 /etc/nginx/geoip/GeoLite2-Country.mmdb {
    auto_reload 5m;
    $geoip2_country_code country iso_code;
}

map $geoip2_country_code $block_country {
    default 0;
    RU      1;
    BR      1;
}

map $http_user_agent $is_search_bot {
    default 0;
    ~*(googlebot|bingbot|baiduspider|yandexbot) 1;
}

map "$block_country:$is_search_bot" $deny {
    default 0;
    "1:0"   1;
}

server {
    if ($deny) { return 444; }
    # ...
    log_format main '$remote_addr $geoip2_country_code "$request"';
}

auto_reload 5m 让 Nginx 每 5 分钟检查 .mmdb 文件是否变化,配合 geoipupdate 的 cron 任务(MaxMind 每周二、周五更新)就能全程免 reload。

为什么要单独放行 Googlebot 这类搜索引擎?

因为按国家整段屏蔽会连正规爬虫一起挡掉,直接影响 SEO 收录。上面 map "$block_country:$is_search_bot" 的作用就是组合两个条件:只有"命中屏蔽国家"且"不是已知搜索引擎 UA"时才返回 1。

注意这是 UA 白名单,可伪造,所以别用它做安全边界,只作为降低误伤的过滤。真正需要严格校验的做法是对 Googlebot 做反向 DNS 验证(host 解析后确认 *.googlebot.com),但 Nginx 原生做不了,得靠 OpenResty 或应用层。

返回 403 还是 444?CDN 场景为什么要加 real_ip?

结论:屏蔽爬虫用 444 优于 403。444 是 Nginx 特有状态码,表示不发送任何响应直接关闭连接,比返回 403 页面省掉响应体和一次往返,对高频扫描的流量成本更低。只有当你想给出提示页或统计错误码时才用 403。

如果你的站前面有 CDN 或负载均衡,$remote_addr 拿到的是 CDN 节点 IP,地理判断会全部错乱。必须在 server 块加:

set_real_ip_from 10.0.0.0/8;
real_ip_header X-Forwarded-For;
real_ip_recursive on;

real_ip 模块会改写 $remote_addr,而 geo 和 geoip2 默认读的就是 $remote_addr,所以顺序上 real_ip 必须在前面生效。另外 GeoLite2 国家级准确率官方标称约 99.8%,但 VPN、机房代理、移动出口 IP 会造成误判,屏蔽前先用 $geoip2_country_code 打进日志跑一周,看真实分布再决定名单。

收个尾:先明确 geo 只是匹配表,认国家要靠 GeoIP2 数据库或现成 CIDR 列表;配置上走 geoip2 → map → if + return 444 三段式,搜索引擎用 UA 白名单组合条件放行,前面有 CDN 记得先配 real_ip。

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

全部回复 0

还没有回复,来抢沙发~