Nginx 实现海内外流量精准分流需依赖外部IP地理库,主流方案是GeoIP2模块配合map指令按country_code映射;geo模块仅适用于静态CIDR段手动分组,不推荐生产环境单独使用。

在 Nginx 中,geo 模块本身不直接识别 IP 地理位置(如国家),它只是一个基于 IP 地址做变量映射的轻量模块。要实现“海外流量与国内流量的精准分流”,必须配合外部 IP 归属地数据库(如 GeoIP2、纯真 IP 库或商业服务),再通过 geo 或更常用的 map 指令结合真实地理信息完成判断。
用 geo 模块做基础 IP 分组(适合小范围、静态规则)
geo 指令适用于已知 CIDR 段的简单划分,比如你手头有明确的中国运营商公网段(如三大运营商部分 ASN 段),可手动维护:
- 只支持 IPv4/IPv6 地址段匹配,不支持域名或动态查询
- 变量值在请求初始化阶段就确定,性能极高
- 不适合全自动识别“中国/海外”,因国内 IP 段分散、海外 IP 无固定规律
示例(仅示意,实际不可靠):
geo $is_china {
default 0;
1.0.1.0/24 1; # 假设这是某段国内教育网
202.96.0.0/16 1; # 假设这是某段电信出口
2001:da8::/32 1; # IPv6 教育网段
}然后在 server 或 location 中使用:if ($is_china) { ... } 或传给后端。但注意:这种写法无法覆盖全量中国 IP,也不具备更新能力,生产环境不推荐单独使用。
主流方案:用 GeoIP2 模块 + map 实现精准国家级分流
Nginx 官方推荐且最可靠的方式是启用 ngx_http_geoip2_module(需编译安装),配合 MaxMind GeoLite2 数据库,按 country_code 判断归属地:
- 下载 GeoLite2 Country MMDB 文件(免费版足够识别 CN/US/JP 等)
- 配置
geoip2指令加载数据库,并提取国家代码到变量(如$geoip2_data_country_code) - 用
map将国家码映射为更易读的分流标识(如cn/oversea)
配置片段示例:
geoip2 /usr/share/GeoIP/GeoLite2-Country.mmdb {
$geoip2_data_country_code source=$remote_addr country iso_code;
}
<p>map $geoip2_data_country_code $traffic_region {
default "oversea";
"CN" "cn";
"HK" "cn";
"TW" "cn";
"MO" "cn";
}之后即可用于条件路由:
server {
location /api/ {
if ($traffic_region = "cn") {
proxy_pass https://internal-cn-backend;
break;
}
proxy_pass https://global-backend;
}
}替代方案:用 Lua + IP 库(OpenResty 场景)
若已部署 OpenResty,可用 resty.iputils 或集成纯真 IP 库(qqwry.dat)做本地查询:
- 支持自定义 IP 数据源,更新灵活(如每日同步 qqwry.dat)
- 可在 Lua 中精确判断“中国”“广东省”“北京市朝阳区”等层级
- 性能略低于 GeoIP2(但实测单机万级 QPS 无压力)
简例:
set_by_lua_block $region {
local ip = ngx.var.remote_addr
local loc = require "qqwry".lookup(ip)
if loc and loc.country == "中国" then
return "cn"
else
return "oversea"
end
}关键注意事项
-
不要用 $remote_addr 直接判断:用户可能走代理、CDN,应优先取
X-Forwarded-For最右非私有 IP,但需确保可信(如只信任你自己的 CDN IP 段) - 区分 IPv4 和 IPv6 处理:GeoIP2 默认支持双栈;若用纯真库,需确认其是否含 IPv6 数据(通常不含)
- 缓存与更新机制:MMDB 文件每月更新,需配合 reload 或共享内存热加载;qqwry.dat 可定时替换+Lua 重载
-
避免在 if 中 proxy_pass:Nginx 的 if 在 location 上下文中有局限,建议改用
map + try_files或直接在 proxy_pass 中用变量(如proxy_pass https://$backend;)


















