GeoIP 地域限制实战:Nginx 屏蔽境外恶意流量

GeoIP 地域限制利用 IP 归属地数据在接入层做访问控制:对于仅面向国内用户的业务,境外流量中绝大部分是扫描器与自动化攻击工具,通过 Nginx GeoIP 模块按国家拦截,可在请求到达应用之前挡掉大量恶意流量。本文给出基于 MaxMind GeoLite2 数据库的完整配置方案。

适用场景

  • 企业官网、管理后台等业务只服务国内用户,无需开放海外访问
  • 境外扫描与爆破流量占比高,浪费带宽并产生大量无效告警
  • 希望在不采购高防产品的前提下降低暴露面

前置条件

  • Nginx 已加载 GeoIP2 模块(检查方式见下)
  • GeoLite2-Country.mmdb IP 归属地数据库
  • Debian/Ubuntu 系统可直接 apt 安装模块;其他发行版或宝塔环境需重新编译 Nginx 加载 ngx_http_geoip2_module

检查模块是否已编译或加载:

nginx -V 2>&1 | tr ' ' '\n' | grep -i geoip

原理说明

GeoIP 模块在 Nginx 解析请求时,将客户端 IP 与本地 IP 归属地数据库(mmdb 格式)比对,得到国家代码,再通过 map 指令生成放行/拦截变量,由 iflocation 决定返回 403 还是正常转发。整个过程在 Nginx 内完成,无外部请求,性能开销极低。

两个关键点:一是数据库需要定期更新,IP 段归属随时间变化,过期数据会产生误判;二是CDN 场景下必须先还原真实客户端 IP,否则判断的是 CDN 节点地址,境内 CDN 会导致规则完全失效。

操作步骤

1. 安装 GeoIP2 模块与数据库

# Debian/Ubuntu(使用发行版 nginx 时)
apt install -y libnginx-mod-http-geoip2

# 下载免费 IP 归属地数据库
mkdir -p /etc/nginx/geoip
cd /etc/nginx/geoip
wget https://github.com/P3TERX/GeoLite.mmdb/raw/download/GeoLite2-Country.mmdb

2. 配置国家判定与放行规则

/etc/nginx/nginx.confhttp 块中加入:

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

map $geoip2_country_code $allowed_country {
    default 0;    # 默认拦截
    CN 1;         # 仅放行中国大陆 IP
}

如需放行香港地区用户,补充一行 HK 1; 即可。

3. 在站点中应用拦截

整站拦截(在 server 块中加入):

if ($allowed_country = 0) {
    return 403;
}

更稳妥的做法是只对后台等敏感路径限制,不影响面向海外搜索引擎的公开页面:

location /admin {
    if ($allowed_country = 0) {
        return 403;
    }
    proxy_pass http://backend;
}

4. CDN/反代场景还原真实 IP

若站点前面套了 CDN,需在 http 块配置 real_ip,将 CDN 回源的 X-Forwarded-For 还原为真实客户端 IP(CDN 网段以服务商公布为准):

set_real_ip_from 0.0.0.0/0;        # 生产环境替换为 CDN 官方网段列表
real_ip_header X-Forwarded-For;
real_ip_recursive on;

5. 配置数据库自动更新

创建 /etc/cron.weekly/update-geoip,每周更新数据库并重载 Nginx:

#!/bin/bash
cd /etc/nginx/geoip
wget -q https://github.com/P3TERX/GeoLite.mmdb/raw/download/GeoLite2-Country.mmdb -O GeoLite2-Country.mmdb.new \
  && mv GeoLite2-Country.mmdb.new GeoLite2-Country.mmdb \
  && nginx -s reload

赋权:chmod +x /etc/cron.weekly/update-geoip

配置验证

先在测试 location 临时输出国家代码,确认判定正确:

location /geo_test {
    return 200 "$geoip2_country_code\n";
}

重载后访问 https://www.example.com/geo_test,国内 IP 应返回 CN。验证通过后删除该 location。

再验证拦截:使用海外 VPS(或让海外朋友)访问受限路径,预期返回 403 Forbidden;国内访问正常返回页面。查看访问日志确认国家代码已记录:

log_format main '$remote_addr - $geoip2_country_code $request $status';
tail -f /var/log/nginx/access.log

常见问题

FAQ 1:套了 CDN 之后拦截不生效?

这是最常见的问题:Nginx 看到的客户端 IP 是 CDN 节点地址,境内 CDN 全部被判为 CN 而放行。必须按步骤 4 配置 set_real_ip_from,且网段列表要与 CDN 服务商公布的回源网段严格一致,否则攻击者可伪造 X-Forwarded-For 头绕过地域限制。

FAQ 2:误拦了海外正常用户或搜索引擎蜘蛛怎么办?

Google、Bing 的爬虫大多来自境外 IP,整站拦截会影响收录。建议二选一:① 只对后台、API 等敏感路径启用地域限制,公开页面放行;② 在拦截规则前对主流搜索引擎蜘蛛 UA 放行(注意 UA 可伪造,仅作为 SEO 兜底,不要对登录入口放行)。另外 GeoLite2 免费库对部分 IP 段归属判定不精确,误报时可先核对数据库更新时间。

总结

GeoIP 地域限制是低成本收敛暴露面的有效手段,配置要点有三:真实 IP 还原决定规则是否真正生效、数据库自动更新决定误判率、按路径粒度限制决定对业务与 SEO 的影响。它属于粗粒度防护,建议与 UA 黑名单、限频配置组合使用,构建多层次的接入防护。