The nginx map
directive is powerful for conditional variable mapping, but its multiple-variable input capability isn't as straightforward as the documentation might suggest. While version 0.9.0+ allows multiple output variables, the input side remains single-variable focused.
The key clarification is that since 0.9.0, you can specify multiple result variables (outputs) from a single mapping block, not multiple input variables. Here's the correct syntax:
map $http_user_agent $bot $np {
default "" "";
"~*Googlebot" "yes" "no";
"~*MJ12bot" "yes" "no";
}
For true multi-variable input conditions, you'll need to chain mappings or use if
in server context:
map $http_user_agent $bot {
default "";
"~*bot" "yes";
}
map "$bot:$request_uri" $bot_np {
default "";
"~*yes:.*/newproject" "bot_newproject";
}
The most effective approach is concatenating variables with delimiters:
map "$bot:$np" $combined_status {
default "normal";
"yes:" "bot_regular";
"yes:yes" "bot_newproject";
":yes" "human_newproject";
}
- Each
map
block still evaluates only one input variable (though it can be concatenated) - Performance impact increases with complex concatenation patterns
- Always test with
nginx -T
for syntax validation
Here's how we implement bot detection with URI pattern matching:
map $http_user_agent $is_bot {
default 0;
"~*bot" 1;
"~*crawl" 1;
}
map "$is_bot:$uri" $request_type {
default "human_regular";
"1:" "bot_regular";
"1:/api" "bot_api";
"0:/checkout" "human_checkout";
}
While working with Nginx's map
directive, many developers encounter confusion about handling multiple variables. The documentation states that since version 0.9.0, multiple variables can be specified, but the implementation isn't as straightforward as one might expect.
The key clarification here is that while Nginx can process multiple variables in map blocks since version 0.9.0, these variables must be processed independently in separate map blocks, not combined in a single mapping operation.
Here's the correct way to handle mappings that depend on multiple variables:
# First map: Identify bots
map $http_user_agent $is_bot {
default 0;
"~*Googlebot" 1;
"~*MJ12bot" 1;
"~*bingbot" 1;
}
# Second map: Check for new project requests
map $request_uri $is_new_project {
default 0;
"~*newproject" 1;
}
# Final map: Combine the results
map "$is_bot$is_new_project" $access_type {
default "regular";
"00" "regular";
"10" "bot";
"01" "new_project";
"11" "bot_new_project";
}
For more complex scenarios, you can concatenate variables in a server or location context:
server {
# ...
set $combined "$http_user_agent:$request_uri";
map $combined $access_type {
default "regular";
"~*Googlebot:.+" "google_bot";
"~*MJ12bot:.+newproject" "mj12_new_project";
# ... additional patterns
}
}
When working with multiple variables:
- Keep map blocks simple and focused
- Place frequently matched patterns earlier
- Avoid complex regex when simple string matching suffices
- Consider using separate maps for independent variables
This pattern is particularly useful for:
# Geo-based access control
map "$geoip_country_code$http_user_agent" $restricted_access {
default 0;
"RU~*Googlebot" 1;
"CN~*bingbot" 1;
}
# Multi-factor rate limiting
map "$remote_addr$http_x_forwarded_for" $client_identifier {
default $remote_addr;
"~^(?.+)\.\1$" $1; # Deduplicate when both match
}