城市维度流量控制是应对精细化运营与安全防护的必要手段,因IP黑白名单和国家粒度控制已无法满足如限定北上广深访问、限制三线以下城市请求等场景需求。

为什么需要城市维度的流量控制
单纯靠 IP 黑白名单或国家粒度(如 $geoip2_country_code)做访问控制,已难以应对精细化运营和安全防护需求。比如:某活动只对北京、上海、深圳用户开放;某接口需限制三线以下城市高频请求;又或发现大量恶意登录来自某个地级市——这时候,城市级地理信息就成为关键判断依据。
前置条件:Nginx 编译支持 geoip2 模块
官方 Nginx 不内置 geoip2,必须通过第三方模块 ngx_http_geoip2_module 扩展。确认方式:
- 执行
nginx -V 2>&1 | grep -o ngx_http_geoip2_module,有输出即已编译接入 - 若无,需重新编译:下载模块源码,添加
--add-module=/path/to/ngx_http_geoip2_module参数,同时确保系统已安装libmaxminddb库 - MaxMind 提供免费版
GeoLite2-City.mmdb(需注册账号下载),建议放在/usr/share/GeoIP/GeoLite2-City.mmdb
在配置中提取并使用城市信息
在 http 块中加载数据库并定义变量:
http {
geoip2 /usr/share/GeoIP/GeoLite2-City.mmdb {
$geoip2_city_name source=$remote_addr country names en;
$geoip2_city_geoname_id source=$remote_addr city geoname_id;
$geoip2_province_name source=$remote_addr subdivisions 0 names en;
$geoip2_country_code source=$remote_addr country iso_code;
}
}关键点说明:
-
source=$remote_addr表示从客户端真实 IP 解析(注意:若前端有 CDN 或代理,需用$http_x_forwarded_for并配合real_ip模块信任上游) -
subdivisions 0取一级行政区(如广东省),city对应城市名(如“Shenzhen”),geoname_id是唯一数字 ID(更稳定,推荐用于匹配) - 中文名需额外下载语言包或改用
names zh-CN,但英文名兼容性更好,建议后端统一处理显示
实战:基于城市的访问控制策略写法
在 server 或 location 块中应用逻辑:
location /api/v1/promo {
# 允许北上广深用户访问
if ($geoip2_city_geoname_id !~ "(2643743|2643741|1819730|1792953)") {
return 403;
}
<pre class="brush:php;toolbar:false;"># 或按城市名精确匹配(注意大小写与空格)
# if ($geoip2_city_name != "Beijing" && $geoip2_city_name != "Shanghai") {
# return 403;
# }
# 结合省份限制:仅限广东省内城市调用
if ($geoip2_province_name != "Guangdong") {
return 403;
}
proxy_pass http://backend;}
更灵活的做法是预定义 map 映射:
map $geoip2_city_geoname_id $is_allowed_city {
default 0;
2643743 1; # Beijing
2643741 1; # Shanghai
1819730 1; # Guangzhou
1792953 1; # Shenzhen
}
<p>server {
location /api/v1/restricted {
if ($is_allowed_city = 0) {
return 403;
}
...
}
}优势:逻辑集中、易维护、避免重复正则解析,性能更优。
验证与排错要点
上线前务必验证数据准确性:
- 用
mmdblookup --file /usr/share/GeoIP/GeoLite2-City.mmdb --ip 202.102.1.1手动查 IP 所属城市,比对 Nginx 日志中$geoip2_city_name是否一致 - 在日志中临时加字段:
log_format main "... $geoip2_city_name $geoip2_city_geoname_id";,观察实际命中情况 - 注意:部分 IP(如内网、未分配、隐私保护 IP)可能无法解析出 city,此时变量为空或为 “-”,需在规则中显式处理(如
if ($geoip2_city_geoname_id = "") { return 403; }) - geoip2 指令不支持在
if内部直接嵌套调用,所有变量必须提前在http块中声明完毕
不复杂但容易忽略细节,一次配准,长期可用。


















