Skip to content

Location matching

Scope: how nginx chooses which location block handles a request — the single most-questioned piece of nginx behaviour, proven here with fixtures instead of asserted.

The contestants

A request path can match several locations at once. The lab server carries one of each kind, and each answers with its own name:

location = /match            { return 200 "winner: exact match =\n"; }
location /match              { return 200 "winner: plain prefix /match\n"; }
location /match/deep/        { return 200 "winner: longer prefix /match/deep/\n"; }
location ^~ /match/anchored/ { return 200 "winner: caret-tilde prefix ^~\n"; }
location ~ ^/match/.*\.txt$  { return 200 "winner: regex ~\n"; }

The results

$ curl -s http://127.0.0.1:8080/match
winner: exact match =
$ curl -s http://127.0.0.1:8080/match/other
winner: plain prefix /match
$ curl -s http://127.0.0.1:8080/match/deep/x
winner: longer prefix /match/deep/
$ curl -s http://127.0.0.1:8080/match/deep/file.txt
winner: regex ~
$ curl -s http://127.0.0.1:8080/match/anchored/file.txt
winner: caret-tilde prefix ^~

The order that produces them

  1. = exact match. If a location = spells the request path precisely, it wins immediately. (/match — exact wins even though prefix and longer candidates exist.)
  2. Longest prefix. Otherwise nginx finds the longest matching prefix location. (/match/other → the plain prefix; /match/deep/x → the longer one.)
  3. ^~ stops the contest. If that longest prefix carries ^~, it wins and regexes are never consulted. (/match/anchored/file.txt ends in .txt — the regex would have matched, but ^~ means "this prefix, and we're done.")
  4. Regexes, in file order. Without ^~, the regex locations (~ case-sensitive, ~* insensitive) are tried top to bottom, and the first match wins — overruling the longest prefix. (/match/deep/file.txt matched the regex despite /match/deep/ being the longer prefix.)
  5. Fallback. No regex matched? The longest prefix from step 2 serves after all.

Location order in the file matters only among regexes; moving prefix blocks around changes nothing. And nested locations and the location / catch-all fit the same scheme: / is simply the shortest possible prefix, the fallback of last resort.

Why this matters in practice

Two everyday consequences:

  • Static-asset shortcuts. The ^~ form exists for "serve these files and skip the regex machinery": location ^~ /static/ { ... } both wins and is fast.
  • The regex ambush. A broad regex early in the file (say, one for .php) will capture requests you expected a prefix block to handle, because step 4 outranks step 2. When a location behaves as if it doesn't exist, check for a regex above it before you check the block itself.

Check: with the rules above, predict the winner for /match/anchored/other (no .txt) and for /MATCH — then, if you have the lab, run them. (Answers: ^~ prefix — the regex wouldn't match anyway, and the prefix contest already ended; and the plain / fallback of the server — matching is case-sensitive, so nothing named here matches /MATCH at all.)