The NGINX map directive is one of the most powerful yet underutilized features in NGINX configuration. It allows you to create variables whose values depend on the values of other variables, enabling complex conditional logic without the performance penalty of multiple if statements. This guide covers everything you need to know about the NGINX map directive, from basic syntax to advanced real-world applications.
What is the NGINX map Directive?
The map directive creates a new variable whose value is determined by mapping the value of a source variable against a set of patterns. Think of it as a highly optimized switch statement or lookup table that runs at near-zero cost during request processing.
The basic syntax is:
map $source_variable $new_variable {
default default_value;
pattern1 value1;
pattern2 value2;
}
The map block must be placed in the http context, outside of any server block. This is because NGINX compiles the map into an efficient hash table during configuration loading, not during request processing.
Why Use the map Directive Instead of if Statements?
Many NGINX administrators reach for if statements when they need conditional logic. However, the if is evil principle in NGINX exists for good reason. The map directive offers several advantages:
Performance: Maps are compiled into hash tables at configuration load time. Lookups are O(1) for exact matches, making them extremely fast regardless of how many entries you have.
Cleaner Configuration: Instead of nested if blocks scattered throughout your configuration, maps centralize conditional logic in one place.
Reusability: A map-defined variable can be used anywhere in your configuration – in server blocks, location blocks, log formats, and more.
Safety: Unlike if statements, maps cannot cause unexpected behavior with other directives. For more on NGINX header configuration, see our guide on the pitfalls of add_header in NGINX.
Basic map Directive Examples
Detecting Mobile Devices
One of the most common uses for the map directive is device detection based on User-Agent:
map $http_user_agent $is_mobile {
default 0;
"~*mobile" 1;
"~*android" 1;
"~*iphone" 1;
"~*ipad" 1;
}
The ~* prefix indicates a case-insensitive regular expression match. You can then use $is_mobile anywhere in your configuration:
server {
listen 80;
server_name example.com;
location / {
if ($is_mobile) {
return 301 https://m.example.com$request_uri;
}
# Regular processing
}
}
Setting Backend Servers Based on Hostname
You can use maps to route requests to different backend servers based on the incoming hostname:
map $host $backend_pool {
default "default_backend";
example.com "production";
staging.example.com "staging";
dev.example.com "development";
}
upstream production {
server 10.0.1.10:8080;
server 10.0.1.11:8080;
}
upstream staging {
server 10.0.2.10:8080;
}
upstream development {
server 10.0.3.10:8080;
}
server {
listen 80;
server_name _;
location / {
proxy_pass http://$backend_pool;
}
}
Understanding map Pattern Matching
The NGINX map directive supports several pattern matching methods, evaluated in a specific order:
1. Exact String Matches
The simplest form matches the source variable exactly:
map $uri $cache_zone {
default "default_zone";
/api/users "users_cache";
/api/products "products_cache";
}
2. Regular Expression Patterns
Prefix a pattern with ~ for case-sensitive regex or ~* for case-insensitive:
map $uri $cache_duration {
default "1h";
"~^/api/v([0-9]+)/" "5m";
"~*[.](jpg|jpeg|png|gif)$" "30d";
"~*[.](css|js)$" "7d";
}
Regular expressions can include capture groups. The captured values are available as $1, $2, etc., within the map value:
map $uri $api_version {
default "v1";
"~^/api/v([0-9]+)/" "v$1";
}
For more details on NGINX patterns, check our article on NGINX location priority.
3. Wildcard Hostname Patterns
When the hostnames parameter is specified, NGINX enables special wildcard matching optimized for domain names:
map $host $site_tier {
hostnames;
default "unknown";
example.com "main";
*.example.com "subdomain";
.example.org "wildcard";
mail.* "mail_server";
}
The hostnames parameter enables two wildcard types:
*.example.com– matches any subdomain (api.example.com, www.example.com).example.com– same as above, shorthand notationmail.*– matches mail.example.com, mail.example.org, etc.
NGINX stores these wildcards in optimized hash tables, making lookups efficient even with thousands of entries.
Pattern Matching Priority
When multiple patterns could match, NGINX evaluates them in this order:
- Exact string match (highest priority)
- Prefix wildcard (
*.example.com) - Suffix wildcard (
www.*) - Regular expressions (in order of appearance)
- Default value (lowest priority)
The first match wins. For regular expressions, order matters – earlier patterns take precedence.
The default Parameter
Every map should include a default value. This value is used when no patterns match:
map $request_method $is_write_method {
default 0;
POST 1;
PUT 1;
PATCH 1;
DELETE 1;
}
Security Warning: Omitting the default can lead to empty variable values, potentially bypassing security controls. Security tools like Gixy specifically check for maps without defaults and flag them as medium-severity issues.
The volatile Parameter
By default, NGINX caches map results per request. The volatile parameter disables this caching:
map $arg_timestamp $cache_buster {
volatile;
default "";
"~.+" $arg_timestamp;
}
Use volatile when:
– The source variable changes during request processing
– You need fresh evaluation each time the variable is accessed
– The map result depends on variables that may be modified by other modules
In most cases, you should not use volatile as the default caching behavior improves performance.
The include Directive Within Maps
For large maps, you can split entries into separate files:
map $http_user_agent $is_bot {
default 0;
include /etc/nginx/maps/known-bots.map;
}
The included file contains plain map entries:
# /etc/nginx/maps/known-bots.map
"~*googlebot" 1;
"~*bingbot" 1;
"~*yandexbot" 1;
"~*baiduspider" 1;
"~*duckduckbot" 1;
This approach is excellent for maintaining large blocklists or whitelists without cluttering your main configuration.
Tuning Map Performance
For configurations with many map entries, NGINX provides two tuning directives in the http context:
http {
map_hash_max_size 2048;
map_hash_bucket_size 128;
}
map_hash_max_size
Sets the maximum size of the hash table used for exact matches. Default is 2048. Increase this if you have many map entries and see warnings in the error log about hash table size.
map_hash_bucket_size
Sets the bucket size for the hash table. Default is typically 64 bytes (one cache line on most systems). Increase to 128 if your map keys are long strings.
These directives affect memory usage and configuration load time, not request processing time. Once loaded, lookups remain O(1). For more on NGINX performance tuning, see our guide on proxy_buffer_size optimization.
Real-World map Directive Use Cases
Rate Limiting with Maps
Create sophisticated rate limiting rules using maps:
# Determine rate limit key based on request type
map $request_uri $rate_limit_key {
default $binary_remote_addr;
"~^/api/" $binary_remote_addr$uri;
"~^/login" $binary_remote_addr;
"~^/static/" "";
}
# Whitelist certain IPs from rate limiting
map $remote_addr $is_whitelisted {
default 0;
127.0.0.1 1;
"~^10[.]" 1;
"~^192[.]168[.]" 1;
}
# Final rate limit key (empty = no limiting)
map $is_whitelisted $final_rate_key {
default $rate_limit_key;
1 "";
}
limit_req_zone $final_rate_key zone=api_limit:10m rate=10r/s;
server {
listen 80;
location /api/ {
limit_req zone=api_limit burst=20 nodelay;
proxy_pass http://backend;
}
}
This configuration:
– Applies different rate limiting to different URL patterns
– Exempts whitelisted IPs entirely
– Skips rate limiting for static files
– Uses a cascading map pattern for complex logic
URL Redirects with Maps
Handle legacy URL redirects efficiently:
map $uri $redirect_target {
default "";
/old-page /new-page;
/legacy/products /shop/products;
"~^/blog/([0-9]+)/(.*)$" /articles/$1/$2;
/discontinued https://archive.example.com/discontinued;
}
server {
listen 80;
server_name example.com;
if ($redirect_target) {
return 301 $redirect_target;
}
# Normal processing continues if no redirect
location / {
try_files $uri $uri/ /index.php?$args;
}
}
This pattern handles hundreds of redirects with minimal configuration and optimal performance.
Dynamic CORS Configuration
Implement secure CORS using maps:
map $http_origin $cors_origin {
default "";
"~^https://(www[.])?example[.]com$" $http_origin;
"~^https://.*[.]example[.]com$" $http_origin;
}
map $cors_origin $cors_methods {
default "";
"~.+" "GET, POST, PUT, DELETE, OPTIONS";
}
map $cors_origin $cors_headers {
default "";
"~.+" "Content-Type, Authorization, X-Requested-With";
}
map $cors_origin $cors_credentials {
default "";
"~.+" "true";
}
server {
listen 443 ssl;
server_name api.example.com;
add_header Access-Control-Allow-Origin $cors_origin always;
add_header Access-Control-Allow-Methods $cors_methods always;
add_header Access-Control-Allow-Headers $cors_headers always;
add_header Access-Control-Allow-Credentials $cors_credentials always;
location / {
if ($request_method = OPTIONS) {
return 204;
}
proxy_pass http://backend;
}
}
This approach only sends CORS headers for allowed origins, preventing information disclosure to unauthorized origins.
A/B Testing Implementation
Implement server-side A/B testing:
# Use split_clients for random assignment
split_clients "${remote_addr}${request_uri}" $ab_random {
50% "A";
* "B";
}
# Override with cookie if user already assigned
map $cookie_ab_variant $ab_variant {
default $ab_random;
"A" "A";
"B" "B";
}
# Select backend based on variant
map $ab_variant $ab_backend {
default "control_backend";
"A" "control_backend";
"B" "experiment_backend";
}
server {
listen 80;
server_name example.com;
location / {
add_header Set-Cookie "ab_variant=$ab_variant; Path=/; Max-Age=86400";
proxy_pass http://$ab_backend;
proxy_set_header X-AB-Variant $ab_variant;
}
}
Content Security Policy Per Route
Apply different security policies to different parts of your application:
map $uri $csp_policy {
default "default-src 'self'; script-src 'self'";
"~^/admin/" "default-src 'self'; script-src 'self' 'unsafe-inline'";
"~^/embed/" "frame-ancestors *";
"~^/api/" "default-src 'none'";
}
server {
listen 443 ssl;
server_name example.com;
add_header Content-Security-Policy $csp_policy always;
location / {
proxy_pass http://backend;
}
}
Cascading Maps Pattern
You can chain multiple maps together for complex conditional logic:
# First map: determine request category
map $uri $request_category {
default "page";
"~^/api/" "api";
"~^/static/" "static";
"~^/admin/" "admin";
}
# Second map: determine cache duration based on category
map $request_category $cache_duration {
default "1h";
api "0";
static "30d";
admin "0";
}
# Third map: determine log format based on category
map $request_category $log_format_name {
default "main";
api "api_detailed";
static "minimal";
admin "admin_audit";
}
This cascading pattern keeps each map focused on a single decision, improving maintainability.
Common Mistakes and How to Avoid Them
Mistake 1: Placing map in Server Context
Maps must be in the http context:
# WRONG - will cause configuration error
server {
map $host $backend {
example.com backend1;
}
}
# CORRECT
http {
map $host $backend {
default backend1;
example.com backend2;
}
server {
# Use $backend here
}
}
Mistake 2: Forgetting the Default
Always include a default to prevent empty variables:
# RISKY - no default
map $http_x_custom_header $allowed {
"secret-token" 1;
}
# SAFE - explicit default
map $http_x_custom_header $allowed {
default 0;
"secret-token" 1;
}
Mistake 3: Incorrect Regex Escaping
Remember that map values are not automatically quoted:
# WRONG - dot matches any character
map $host $backend {
api.example.com "api_backend";
}
# CORRECT for literal matching (no regex needed for exact match)
map $host $backend {
default "default_backend";
api.example.com "api_backend";
}
# CORRECT for regex with literal dot
map $host $backend {
default "default_backend";
"~^api[.]example[.]com$" "api_backend";
}
Mistake 4: Using Regular Expressions When Not Needed
Exact matches are faster than regex:
# SLOWER - unnecessary regex
map $request_method $is_post {
default 0;
"~^POST$" 1;
}
# FASTER - exact match
map $request_method $is_post {
default 0;
POST 1;
}
Debugging map Variables
To debug map variable values, add them to your log format or response headers:
log_format debug_maps '$remote_addr - $request_uri - '
'mobile=$is_mobile backend=$backend_pool';
server {
access_log /var/log/nginx/debug.log debug_maps;
location / {
add_header X-Debug-Mobile $is_mobile;
add_header X-Debug-Backend $backend_pool;
# ...
}
}
You can also use curl to test your maps:
# Test mobile detection
curl -H "User-Agent: Mozilla/5.0 (iPhone)" http://localhost/ -I
# Test hostname routing
curl -H "Host: staging.example.com" http://localhost/ -I
Security Considerations
Validate map Configurations with Gixy
Gixy is an NGINX configuration analyzer that checks for common security issues, including maps without defaults. Run it as part of your deployment pipeline:
gixy /etc/nginx/nginx.conf
Gixy will warn you about:
– Maps without default values
– Potentially dangerous regular expressions
– Variables that could contain dangerous characters
Avoid User Input in Sensitive Contexts
Be careful when map results are used in security-sensitive contexts:
# POTENTIALLY DANGEROUS if $uri contains user input
map $uri $allowed_origin {
"/api/public" "*";
default "";
}
add_header Access-Control-Allow-Origin $allowed_origin;
Always validate that your map patterns cannot be exploited through crafted input.
Performance Benchmarks
To understand map performance, consider that:
- Exact match lookups: O(1) hash table lookup, typically under 100 nanoseconds
- Wildcard lookups: O(1) with optimized hash tables for DNS patterns
- Regex lookups: O(n) where n is the number of regex patterns, evaluated sequentially
For a map with 1000 exact entries, lookup time is essentially the same as a map with 10 entries. However, 1000 regex patterns will be significantly slower than 10.
Conclusion
The NGINX map directive is an essential tool for any serious NGINX administrator. It provides a clean, performant, and maintainable way to implement conditional logic in your NGINX configuration. By mastering maps, you can:
- Eliminate messy
ifstatement chains - Implement sophisticated routing and rate limiting
- Create dynamic security policies
- Build A/B testing infrastructure
- Manage URL redirects efficiently
Remember to always include default values, use exact matches when possible, and validate your configurations with tools like Gixy. With these practices in place, the map directive becomes one of the most valuable tools in your NGINX toolkit.

