Phase order explains why directives that look sequential aren't, and avoids the infamous if pitfalls.
NGINX configuration is declarative, not a script. Each HTTP request passes through fixed phases, and each module's directives run in the phase it registered for, regardless of the order you wrote them:
server_rewrite(rewrite directives at server level),find_config(choose the location),rewrite(rewrite directives in the location),post_rewrite(restart location search if the URI changed),preaccess(limit_req, limit_conn),access(allow/deny, auth_basic, auth_request),post_access(satisfy),precontent(try_files),content(proxy_pass, static files,returnis actually in rewrite), andlog.
So return runs in the rewrite phase, before allow/deny: a return 200 in a location is served even to denied clients.
if belongs to the rewrite module. Inside a location, a true if creates a hidden nested location and the request is handled by that configuration, which inherits some directives and silently drops others such as try_files. The only fully safe things inside if in a location are return and rewrite ... last.
`map` is the idiomatic alternative: it defines a variable computed from others, evaluated lazily only when used, so unused maps cost nothing. Variables like $limit_key from a map can then feed limit zones, logging conditions or proxy headers without any if.
Example
map $http_x_api_key $limit_key { # http context
default $binary_remote_addr; # anonymous: limit by IP
"~^key_" $http_x_api_key; # API clients: limit by key
}
map $status $loggable { # don't log 2xx/3xx
~^[23] 0;
default 1;
}
limit_req_zone $limit_key zone=api:10m rate=20r/s;
server {
listen 80;
access_log /var/log/nginx/errors.log combined if=$loggable;
location /api/ {
limit_req zone=api burst=40 nodelay;
deny all;
return 200 "ok\n"; # still served: return runs before access checks
}
}Remember
- Directives run in their phase, not in file order
- return and rewrite run before access checks
- Inside location, use if only for return or rewrite ... last; prefer map
Watch out for
- Putting proxy_pass or try_files logic inside if blocks and getting inexplicable behaviour.