How nginx matches a request: locations and rewrite phases

Exact, prefix and regex location precedence, the order of the rewrite and access phases, return versus rewrite, and a method for reading an unfamiliar config.

Location matching precedence

server {
    listen 443 ssl;
    server_name example.com;
    root /var/www/site;

    location = /health {          # 1. exact match, wins immediately
        return 200 "ok\n";
        add_header Content-Type text/plain;
    }

    location ^~ /static/ {        # 2. longest prefix that stops regex checking
        expires 30d;
        access_log off;
    }

    location ~* \.(png|jpg|webp)$ {   # 3. first matching regex in file order
        expires 7d;
    }

    location /api/ {              # 4. longest plain prefix if no regex matched
        proxy_pass http://127.0.0.1:8080;
    }

    location / {                  # 5. the catch-all
        try_files $uri $uri/ /index.html;
    }
}
  • Order of evaluation: exact match, then longest ^~ prefix, then regexes in the order written, then the longest plain prefix.
  • A regex location is checked as soon as no exact or ^~ match applies, so a broad regex can steal requests from a more specific prefix.
  • = is a single string comparison and is the cheapest match available — ideal for health checks and hot paths.
  • Nested locations are matched against the URI, not against the parent's file system path.

The request phases in order

PhaseWhat runsTypical directive
post-readAfter the request lineReal IP resolution
server-rewriteServer-level rewritingrewrite in server
find-configChoose the locationn/a
rewriteLocation-level rewritingrewrite, return
accessAuthorisationallow, deny, auth_basic, limit_req
contentProduce the responseproxy_pass, index, try_files
logWrite the access entrylog_format
# return ends the request immediately, in the rewrite phase,
# before access controls run. It is cheap and cannot be bypassed by a file.
location = /old-page {
    return 301 https://example.com/new-page;
}

# an internal rewrite re-runs location matching, which is why it costs more
location /legacy/ {
    rewrite ^/legacy/(.*)$ /new/$1 last;
}
⚠️
A return in the rewrite phase happens before allow and deny. If you rely on an access rule to protect a path you also redirect, the redirect fires first and the protection never runs.

Reading an unfamiliar config

# 1. what is the fully expanded config, in order?
nginx -T > /tmp/expanded.conf

# 2. which server handles this host?
grep -n "server_name" /tmp/expanded.conf

# 3. which location matches this URI? add a marker and reload
#    location = /debug-match { return 200 "matched exact\n"; }

# 4. test with a real request and inspect the result
curl -sSI https://example.com/static/app.css | head -20
curl -sS -o /dev/null -w '%{http_code} %{time_total}s\n' https://example.com/api/health

Work top down: the server block by server_name and listen port, then the location by the precedence rules, then the directives inside it, remembering that inherited values apply where the location does not override them.

FAQ

What does <code>^~</code> actually change?
It stops nginx from evaluating regex locations when that prefix matches. Use it for static asset directories so a broad asset regex cannot take precedence and apply the wrong headers.
Why does my regex location never match?
A longer prefix with ^~ matched first, or the regex is checked after an exact match. Confirm the evaluation order before debugging the pattern itself.

Rewrites, redirects and try_files Installing nginx and understanding the configuration layout

Last refreshed 2026-09-18.