Geo / map / variable cheatsheet.
map
map $http_user_agent $is_mobile {
default 0;
~*android 1;
~*iphone 1;
~*ipad 1;
}
server {
location / {
if ($is_mobile) {
return 301 https://m.example.com$request_uri;
}
}
}
map with multiple values
map $http_accept_language $lang {
default "en";
~^en "en";
~^es "es";
~^de "de";
}
map for cache key
map $request_method $cache_method {
GET 1;
HEAD 1;
default 0;
}
geo
geo $internal {
default 0;
10.0.0.0/8 1;
192.168.0.0/16 1;
127.0.0.1 1;
}
server {
location /admin {
if ($internal = 0) { return 403; }
}
}
By IP, not header.
GeoIP2 (country lookup)
apt install libnginx-mod-http-geoip2
http {
geoip2 /etc/nginx/GeoLite2-Country.mmdb {
auto_reload 5m;
$geoip2_country_code default=XX country iso_code;
$geoip2_country_name country names en;
}
map $geoip2_country_code $allowed {
default 1;
CN 0;
RU 0;
}
server {
if ($allowed = 0) { return 451; }
}
}
split_clients (A/B)
split_clients $request_id $variant {
50% A;
50% B;
}
map $variant $upstream {
A app_a;
B app_b;
}
location / {
proxy_pass http://$upstream;
}
Custom variables via map
map $request_method:$content_type $cache_action {
default "skip";
"GET:" "cache";
"GET:application/json" "cache";
}
include map files
http {
map $http_user_agent $bot {
include /etc/nginx/maps/bots.conf;
}
}
/etc/nginx/maps/bots.conf:
default 0;
~*Googlebot 1;
~*Bingbot 1;
~*DuckDuckBot 1;
Boolean operators (workaround)
if doesn’t support &&, ||. Workaround:
set $combined "$is_mobile-$is_authenticated";
if ($combined = "1-0") {
# mobile + anonymous
return 301 /mobile-login;
}
Or use map:
map "$is_mobile:$is_authenticated" $action {
"1:0" "mobile-login";
"1:1" "mobile-home";
default "desktop";
}
Variables in proxy_pass
location / {
proxy_pass http://$upstream; # $upstream from map
}
⚠️ With variable, nginx skips upstream block caching of resolution; uses runtime resolver:
resolver 1.1.1.1 valid=300s;
limit_req_zone with map
map $http_x_skip_limit $no_limit {
default 0;
"secret-token" 1;
}
map $no_limit $rate_key {
0 $binary_remote_addr;
1 "";
}
limit_req_zone $rate_key zone=api:10m rate=10r/s;
$upstream_addr / $upstream_status
Log which backend served:
log_format main '... upstream=$upstream_addr status=$upstream_status rt=$request_time';
Useful for debugging LB.
$request_id
add_header X-Request-ID $request_id;
proxy_set_header X-Request-ID $request_id;
UUID per request. Correlate logs across nginx + app.
Common variables
$args # query string
$arg_NAME # specific query arg
$cookie_NAME # cookie
$http_NAME # request header
$sent_http_NAME # response header
$upstream_http_NAME # upstream response header
$host # Host header
$server_name
$server_port
$scheme
$remote_addr
$remote_port
$remote_user # basic auth user
$request # full request line
$request_method
$request_uri # full URI (path + query)
$uri # decoded URI (no query)
$document_root
$document_uri
$body_bytes_sent
$bytes_sent
$status
$request_time
$request_length
$pid
$hostname
$msec # high-res time
$time_iso8601
$time_local
Common mistakes
ifinsidelocation— nginx wiki says “If is evil.” Usemaporreturnwhen possible.- Map names overlap with vars — confusing.
- GeoIP2 mmdb path wrong / not auto-reloaded.
- Forgetting
resolverfor variableproxy_pass. $argsvs$query_string(synonyms).
Read this next
If you want my A/B testing + GeoIP recipes, they’re at rajpoot.dev .
Building something AI-, backend-, or data-heavy and want a second pair of eyes? I do consulting and freelance work — see my projects and ways to reach me at rajpoot.dev .