nginx built-in variables
nginx exposes a large set of $variables you can reference in directives like
log_format, proxy_set_header, return, map and if. They expose request
metadata, proxy and upstream details, and TLS information. This reference groups
the common ones by source module and explains each.
How it works
Variables are evaluated per request. Some are fixed names ($remote_addr,
$status); others follow a dynamic pattern where part of the name is supplied
by you:
log_format main '$remote_addr "$request" $status '
'$body_bytes_sent "$http_user_agent" '
'rt=$request_time urt=$upstream_response_time';
location /api/ {
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://backend;
}
The dynamic prefixes are $http_NAME (request headers), $sent_http_NAME
(response headers), $arg_NAME (query arguments) and $cookie_NAME (cookies),
where NAME is lowercased with hyphens replaced by underscores.
Module groups at a glance
| Module | Typical variables |
|---|---|
ngx_http_core_module | $uri, $request_uri, $host, $remote_addr, $status, $request_time |
ngx_http_proxy_module | $proxy_add_x_forwarded_for, $proxy_host |
ngx_http_upstream_module | $upstream_addr, $upstream_status, $upstream_response_time, $upstream_connect_time |
ngx_http_ssl_module | $ssl_protocol, $ssl_cipher, $ssl_client_s_dn, $ssl_server_name |
ngx_http_geo_module / ngx_http_geoip_module | $geoip_country_code, $geoip_city |
ngx_http_realip_module | $realip_remote_addr |
Common real-world patterns
Structured access log for a reverse proxy
log_format json_combined escape=json
'{"time":"$time_iso8601",'
'"remote_addr":"$remote_addr",'
'"method":"$request_method",'
'"uri":"$uri",'
'"status":$status,'
'"bytes":$body_bytes_sent,'
'"rt":$request_time,'
'"urt":"$upstream_response_time",'
'"ua":"$http_user_agent"}';
This logs every request as a JSON object, which most log-aggregation systems (Loki, Elasticsearch, Datadog) can ingest without a parser.
Passing real client IP through a load balancer
# upstream load balancer sets X-Real-IP; nginx trusts it and exposes it
real_ip_header X-Real-IP;
set_real_ip_from 10.0.0.0/8;
location / {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Real-IP $remote_addr;
proxy_pass http://backend;
}
After set_real_ip_from, $remote_addr reflects the original client IP rather than the load balancer’s IP.
Routing by TLS version or cipher
if ($ssl_protocol = "TLSv1") {
return 403 "TLS 1.0 not accepted";
}
$ssl_protocol is only populated inside an ssl server block after the handshake completes.
Conditional caching based on a cookie
map $cookie_session $no_cache {
"" 0;
default 1;
}
proxy_no_cache $no_cache;
proxy_cache_bypass $no_cache;
$cookie_NAME gives the value of any named cookie without a separate add_header round trip.
Tips and notes
$request_urikeeps the query string and original form;$uriis normalised and rewrite-aware. After a rewrite they diverge — use$uriinproxy_passand$request_urifor logging the client’s original request.- Use
$proxy_add_x_forwarded_for(not$remote_addr) to correctly chain the IP through multiple nginx tiers. - Upstream timing variables (
$upstream_response_time,$upstream_connect_time) are populated after the upstream finishes. They are empty — not zero — if the upstream was never contacted (for example, a cached response). - The
$ssl_family only has values on HTTPS server blocks with TLS fully negotiated. Referencing them in an HTTP block produces an empty string, not an error. - Variables inside
ifblocks have some caveats in nginx’s config model; prefermapfor complex conditional logic.