map指令必须定义在http块顶层,不可嵌套于server或location中;其源变量需为字符串型且求值早于geoip2模块,匹配严格区分大小写、不支持通配符,proxy_pass须动态引用已声明的upstream名称。

map 指令必须在 http 块顶层定义,不能嵌套在 server 或 location 里
这是最常踩的坑:很多人把 map 写在 server 块里,Nginx 启动直接报错 unknown directive "map"。因为 map 是变量映射指令,属于变量预处理阶段,只能出现在 http 上下文中。
正确做法是:在 /etc/nginx/nginx.conf 的 http { ... } 块最外层(通常在 include 语句之后、server 块之前)定义:
http {
# 必须在这里
map $geoip_country_code $backend_upstream {
default "default_backend";
CN "cn_backend";
JP "jp_backend";
US "us_backend";
}
<pre class='brush:php;toolbar:false;'>upstream cn_backend { server 10.0.1.10:8080; }
upstream jp_backend { server 10.0.2.20:8080; }
upstream us_backend { server 10.0.3.30:8080; }
upstream default_backend { server 10.0.0.100:8080; }
server { ... }}
geoip_country_code 变量依赖 ngx_http_geoip_module 或 geoip2 模块
原生 Nginx 不自带地理信息变量。CentOS/RHEL 默认编译不带 ngx_http_geoip_module,Ubuntu/Debian 的 nginx-full 包可能含,但更推荐用 ngx_http_geoip2_module(支持 GeoLite2,精度更高、更新及时)。
确认模块是否加载:
nginx -V 2>&1 | grep -o with-http_geoip2_module
若未启用,需重新编译或安装对应包(如 Ubuntu 上:sudo apt install libnginx-mod-http-geoip2),然后配置数据库路径:
geoip2 /usr/share/GeoIP/GeoLite2-Country.mmdb { $geoip2_data_country_code source=$remote_addr country iso_code; }- 注意:必须用
$geoip2_data_country_code这类自定义变量名,再把它喂给map;不能直接在map中引用$geoip2_...—— 因为map的源变量必须是字符串型且求值时机早于 geoip2 模块解析
map 的匹配是严格字符串比对,大小写敏感且不支持通配符
map 不是正则匹配,它只做精确字符串映射。比如你写了 CN "cn_backend",那么只有 $geoip_country_code 值为 CN(全大写、无空格、无换行)才会命中。
常见陷阱:
- GeoIP 数据库返回的是 ISO 3166-1 alpha-2 码,
CN正确,China或cn都不匹配 - 某些代理透传了错误的
X-Forwarded-For,导致$remote_addr是内网 IP,geoip 查不到,最终走default分支——务必用real_ip模块还原真实客户端 IP - 如果需要按大区路由(如“亚洲”包含 CN/JP/KR),得手动枚举:
CN "asia_backend"; JP "asia_backend"; KR "asia_backend";
后端转发必须用变量名而非字面值,且 upstream 名要提前声明
在 location 块中写 proxy_pass http://$backend_upstream; 才能触发动态路由。写成 proxy_pass http://cn_backend; 就是静态的,完全绕过了 map。
关键约束:
-
$backend_upstream的值必须是已声明的upstream块名称,拼错或漏定义会导致 502 Bad Gateway -
proxy_pass后不能加 URI 路径(如http://$backend_upstream/api),否则变量会被忽略,Nginx 当作字面量处理并报错invalid URL prefix - 若需重写路径,应配合
rewrite或proxy_redirect,而不是塞进proxy_pass地址里
真正起作用的转发逻辑就这一行:proxy_pass http://$backend_upstream;,其余都是铺垫。容易被忽略的是:所有 upstream 块必须在 map 定义之后、server 块之前完成声明,顺序错乱会导致变量未识别。



















