Nginx Map Directive: How to Use Multiple Variables as Input Parameters


1 views

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
}