Request phases, if and map

NGINX · Expert · 14 min

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, return is actually in rewrite), and log.

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.

Check yourself

  1. A location contains deny all; and return 200 "ok";. What does a client get?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: nginx.org/en/docs/dev/development_guide.html#http_phases