Nginx 的 geo 模块按国家/地区屏蔽爬虫的配置方法。
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。
转载请注明出处,版权归原作者所有。