In nginx, several `location` blocks in one server could match the same request path. In what order does nginx evaluate exact (`=`), prefix, `^~` and regular-expression locations, and which one ends up handling the request?
answer
- file order is not match order
- one location wins, never two
- longest prefix is only remembered
- ^~ ends the regex phase
- regexes tried top to bottom
basics
~20 sNginx tries exact (=) locations first. Otherwise it remembers the longest matching prefix: a ^~ prefix wins immediately; otherwise regular expressions are tested in configuration-file order and the first match wins, falling back to that remembered prefix.
solid answer
~40 sNginx picks exactly one location per request, and the order is not the order of the file. First it looks for an exact match — `location = /health` — and if one matches, that location serves the request and the search stops. Otherwise nginx scans all prefix locations and *remembers* the longest one that matches. If that longest prefix carries the `^~` modifier, nginx stops there and never evaluates regexes. Otherwise it tests regular-expression locations (`~` case-sensitive, `~*` case-insensitive) in the order they appear in the config, and the first regex that matches wins. Only if no regex matches does the remembered prefix serve the request. The practical consequence: a short regex anywhere in the file can steal traffic from a much longer, more specific prefix, and `^~` is how you protect that prefix.
code
nginx · 21 linesserver {
listen 80;
server_name example.com;
location = /health {
return 200 "ok\n";
}
location /docs/ {
root /srv/site;
}
location ~* \.(png|jpg|css)$ {
expires 30d;
root /srv/site;
}
location / {
return 404;
}
}go deeper
Know that nginx picks one location per request and that = means an exact path match. Be able to point at the catch-all location / and say it is the last resort, not the first thing tried.
Explain all five steps in order and, crucially, that a plain longest prefix is only remembered while regexes are evaluated first. Name ^~ as the modifier that ends the regex phase.
Diagnose the real incident: a broad asset regex quietly capturing a directory that needed its own root or headers. Show how you confirmed the winner from logs rather than by reading the file top to bottom.
Own the convention. Decide whether the estate routes by prefix with ^~ on protected trees or leans on regexes, and argue the cost: regexes are order-dependent across included files, so they make a large config's behaviour depend on include order.
## What "matching" means in nginx Once nginx has chosen a `server` block for a request, it chooses exactly **one** `location` block inside it. Locations are not layered and not merged: the winner's directives (plus whatever it inherits from `server` and `http`) become the configuration for that request. So "which location wins" is not a style question — it decides which root, which handler, which headers and which limits apply. ## The algorithm, step by step 1. **Exact match.** If a `location = /uri` matches the request path character for character, that location is used and the search **stops immediately**. This is why `= /` or `= /health` is the cheapest possible route. 2. **Prefix scan.** Nginx tests every prefix location (no modifier, or `^~`) and *remembers* the **longest** one that matches. It does not use it yet. 3. **The `^~` short-circuit.** If the remembered longest prefix was declared with `^~`, nginx stops here and uses it — regular expressions are never evaluated. 4. **Regular expressions.** Otherwise nginx tests `~` (case-sensitive) and `~*` (case-insensitive) locations **in the order they appear in the configuration file**, top to bottom. The **first** one that matches wins outright. 5. **Fallback.** If no regex matched, the prefix remembered in step 2 is used. ```nginx server { location = /health { return 200 "ok\n"; } # 1: exact, stops the search location ^~ /assets/ { root /srv/site; } # 3: prefix that blocks regexes location ~* \.(png|jpg)$ { expires 30d; } # 4: regexes, in file order location /docs/ { root /srv/site; } # 2: plain prefix, only remembered location / { return 404; } # catch-all prefix } ``` ## The trap candidates fall into Given `location /docs/` and `location ~* \.png$`, a request for `/docs/diagram.png` is handled by the **regex**, not by the longer prefix — because step 2 only *remembers* the prefix and step 4 runs before step 5. Teams discover this when a global `\.(png|jpg|css)$` block silently takes over a directory that was supposed to have its own root, auth or caching rules. The fix is either to declare the directory as `location ^~ /docs/` so the regex phase is skipped, or to narrow the regex so it no longer covers that path. The mirror-image trap is assuming file order matters for prefixes. It does not: prefixes are compared by length, so moving `location /` to the top of the file changes nothing. File order matters **only** among regexes. ## Modifiers worth naming precisely - *(none)* — plain prefix; competes on length, evaluated last. - `=` — exact; the whole normalized path must equal the value. - `^~` — prefix that suppresses the regex phase if it is the longest match. It is **not** a regex, and the `^` is not an anchor. - `~` / `~*` — POSIX regex, case-sensitive / case-insensitive. - `@name` — a *named* location. It does not take part in this search at all; it is reachable only through an internal redirect from `try_files`, `error_page` and similar. ## Nesting A prefix location may contain nested locations. Once the outer location is selected, nginx repeats the same search among the nested ones. Nesting is a scoping tool, not a way to make two locations both apply. ## Proving which one matched The cheapest reliable technique is to tag each location and log the tag: ```nginx log_format tagged '$remote_addr "$request" loc=$loc'; access_log /var/log/nginx/access.log tagged; location /docs/ { set $loc docs; ... } location ~* \.png$ { set $loc png; ... } ``` On a binary built with `--with-debug` you can also set `error_log ... debug;` and read the `test location:` lines nginx emits while it walks the tree. Guessing from the file top to bottom is exactly the habit the question is testing for.
- Where do nested locations fit into that order?A prefix location can contain locations of its own. Nginx first runs the normal search at the outer level; if the winner is a prefix location containing nested locations, it repeats the same algorithm among them. Nesting narrows the search after a winner is chosen — it never lets two locations both contribute directives to one request.
- Does a named location such as `@fallback` compete in this matching at all?No. Named locations are excluded from request matching entirely — no request path ever selects one. They exist only as internal redirect targets, reached from the last argument of `try_files`, from `error_page`, and from a few similar directives. That is why a named location can safely contain a handler that would otherwise loop.
- How would you prove which location actually handled a given request in production?Add `set $loc <tag>;` inside each candidate location and include `$loc` in a custom `log_format`, then read the access log. On a debug-enabled build you can also raise `error_log` to `debug` and read the `test location:` lines, but that is heavy for a busy server — the tagged access log costs almost nothing.
saying these in an interview costs you the question
- Says the first matching location in the file wins
- Thinks the longest prefix always beats a regex
- Believes several locations can both apply to one request
- Reads ^~ as a case-insensitive or anchored regex
- Thinks reordering prefix locations changes which one matches