Nginx的配置里,if语句一直是被官方文档明确警告尽量少用的东西,判断逻辑一多,配置就会变得难以维护,甚至踩出奇怪的坑。其实在大多数需要做条件判断的场景下,map指令才是更合适的工具。它属于ngx_http_map_module模块,默认就会编译进Nginx,作用一句话概括:根据源变量的值,按你定义的映射表,生成一个新变量。整个过程在变量第一次被求值时才执行,性能开销极低。这篇文章就来把map的语法细节和几个典型的高级用法讲透。

map指令的基础语法与匹配规则
先看最基本的写法。map指令必须放在http块中,语法结构如下:
map $http_host $backend_pool {
default web_default;
api.ippipp.com api_servers;
~*^img\. image_servers;
}这段配置的含义是:源变量为$http_host,目标变量为$backend_pool。花括号里是映射表,每行左边是匹配条件,右边是映射结果。当请求的Host是api.ippipp.com时,$backend_pool的值就是api_servers,可以直接用在proxy_pass http://$backend_pool;这样的位置。
匹配规则有几点需要特别留意。第一,map会按映射表中出现的顺序逐条尝试,命中即停止,所以具体的规则要写在宽泛的规则前面。第二,字符串匹配默认是大小写敏感的,如果想让host不区分大小写,可以加hostnames;参数简化域名写法,或者用正则配合~*前缀表示不区分大小写。第三,正则匹配用~开头表示区分大小写,~*表示不区分,命中后还可以用$1、$2引用捕获组,这一点在做路径改写时非常有用。
另外还有几个参数值得记住。default指定所有规则都不命中时的兜底值,不写的话结果就是空字符串。volatile表示该变量不做缓存,每次都重新求值,一般用不到但要知道有这个开关。strings和hostnames这类标记写在映射表最前面,用于声明整体的匹配行为。理解了这些,后面的实战案例就好懂了。
实战案例一:按设备类型与业务线做分流
最常见的需求是根据User-Agent把移动端和PC端流量分到不同的上游集群。用if写起来很别扭,用map则干净利落:
map $http_user_agent $is_mobile {
default 0;
~*android 1;
~*iphone 1;
~*ipad 0;
~*micromessenger 1;
}
upstream mobile_backend {
server 10.0.1.10:8080;
}
upstream pc_backend {
server 10.0.1.20:8080;
}
server {
listen 80;
location / {
proxy_pass http://$is_mobile_backend; # 这种写法不行,见下方说明
}
}上面代码最后一行故意写了一个常见错误:$is_mobile_backend这样的拼接是不存在的变量。正确的做法是再定义一层map,或者利用映射结果直接返回上游名称:
map $http_user_agent $device_backend {
default pc_backend;
~*android mobile_backend;
~*iphone mobile_backend;
}
server {
listen 80;
location / {
proxy_pass http://$device_backend;
}
}这样配置后,Nginx会根据请求头自动选择上游,无需任何if判断。同样的思路还可以用于灰度发布:比如在map中把特定IP段、特定cookie值映射到新版本集群,其余流量走老集群,回滚时只需改一行映射关系再reload即可,比改代码发版轻量得多。要注意的是,proxy_pass中使用变量时,Nginx不会自动做DNS解析,上游地址最好用upstream块定义,或在map的值里写死解析器可解析的域名。
实战案例二:限流白名单与缓存key定制
第二个场景是限流豁免。公司内网、监控探针、合作伙伴的IP往往不能被限流策略误伤,用map生成一个空的key就能实现白名单:
map $remote_addr $limit_key {
default $binary_remote_addr;
10.0.0.1 "";
~^192\.168\. "";
~^172\.(1[6-9]|2[0-9]|3[01])\. "";
}
limit_req_zone $limit_key zone=req_zone:10m rate=10r/s;
server {
location /api/ {
limit_req zone=req_zone burst=20 nodelay;
proxy_pass http://api_backend;
}
}这里的关键在于,limit_req模块遇到空字符串key时会直接跳过限流,所以白名单IP映射成空串即可,其余IP映射成$binary_remote_addr参与正常限流。正则中的反斜杠用于转义点号,注意192\.168\.这种写法要保持原样,不能丢掉反斜杠。
第三个用法是定制缓存key。例如希望登录用户永远不命中缓存、未登录用户按URL维度缓存,可以这样组合:
map $cookie_token $cache_key {
default "no-cache-$remote_addr-$request_id";
"" "$scheme$request_method$host$uri$is_args$args";
}
proxy_cache_path /data/nginx_cache levels=1:2 keys_zone=page_cache:50m;
server {
location / {
proxy_cache page_cache;
proxy_cache_key $cache_key;
proxy_cache_valid 200 10m;
proxy_pass http://web_backend;
}
}这段配置里,没有token cookie的请求按完整URL作为缓存key,享受缓存加速;携带token的请求key里掺入了$request_id,每个请求都唯一,等于事实上的不缓存。map的值本身还可以引用其他Nginx变量,这种变量套变量的组合能力,正是它比if灵活得多的原因。
使用map时的注意事项与性能建议
虽然map很强大,但有几个坑要避开。首先是求值时机:map变量是惰性求值的,只有请求中真正用到该变量时才会执行匹配,定义再多map不会拖累性能,但配置复杂时可读性会下降,建议给每个map加注释说明用途。其次,map不能放在server或location块中,只能放在http块,跨多个server共用时要注意命名不要冲突。
其次是转义问题。映射表里的正则若包含花括号,比如匹配三位数字\d{3},会导致Nginx解析配置时报错,因为花括号被当成了map块的边界,这时需要用引号把整个正则包起来:"~^\d{3}\." value;。同理,映射值里包含分号或空格时也要加引号。
最后说一下map与set、if的取舍。简单的变量赋值用set即可;一旦涉及多分支判断,一律优先map。它不仅让配置线性可读,还天然避免了if在location中配合proxy_pass、rewrite时那些著名的不生效问题。把分支逻辑收敛到http块的几张map表里,server和location部分保持干净,这是维护大规模Nginx配置的一个非常实用的习惯。
Nginx map指令Nginx变量映射map模块配置修改时间:2026-09-04 16:45:00