Lesson 1

How nginx picks a location block

A request arrives and several location blocks match it. nginx does not take the first one written, and it does not take the most specific one either. It runs five fixed steps, and the order of those steps is what determines the winner.

The five steps

  1. Is there a = /path block that matches exactly? If so it wins immediately and nothing else is considered.
  2. Find the longest matching prefix block, including blocks carrying ^~. Remember it; do not use it yet.
  3. Does that longest prefix carry ^~? If so, use it and skip every regex.
  4. Try the regexes in the order they appear in the file. The first one that matches wins.
  5. If no regex matched, fall back to the prefix block remembered in step 2.
Step 4 goes by file order, not by specificity A narrower regex written below a broader one never runs. The measured configuration below contains exactly that case: block 4 does not win on any of the 14 paths tried, and nginx still starts normally without emitting a warning.

Measured on a real nginx

The configuration below was loaded into an nginx:1.27-alpine container. Each block returns its own name, and curl was then called for 14 paths in turn.

location = /exact          { return 200 "1 exact"; }
location ^~ /images/       { return 200 "2 prefix-priority"; }
location ~ \.(gif|jpg)$    { return 200 "3 regex-gif-jpg"; }
location ~ ^/img/.*\.gif$  { return 200 "4 regex-img-gif"; }
location ~* \.PNG$         { return 200 "5 regex-png-ci"; }
location /img/             { return 200 "6 prefix-img"; }
location /doc              { return 200 "7 prefix-doc"; }
location /document         { return 200 "8 prefix-document"; }
location /                 { return 200 "9 default"; }
PathWinning blockWhy
/exact1 exactexact match, step 1
/exact/9 defaultan exact match is byte-for-byte; one extra / is a different URI
/images/a.gif2 prefix-priority^~ blocks the regexes at step 3, even though block 3 also matches
/images/b.txt2 prefix-prioritysame block; here no regex matches anyway, so the two results coincide
/img/a.gif3 regex-gif-jpgthe earlier regex wins; block 4 is narrower but never runs
/img/a.GIF6 prefix-imgblocks 3 and 4 both use ~, which is case-sensitive, so the request drops to step 5
/img/b.txt6 prefix-imgno regex matches, so back to the prefix at step 5
/photo.gif3 regex-gif-jpgthe regex matches
/photo.PNG5 regex-png-ci~* is case-insensitive
/photo.png5 regex-png-ciblock 3 only covers gif and jpg
/doc7 prefix-docthe prefix matches
/document8 prefix-documentthe longer prefix wins
/documentation8 prefix-documentstill the longest prefix
/other9 defaultonly / is left
An error hit while building this test rig The first version of the configuration had both location ^~ /images/ and location /images/. nginx refused to start: [emerg] duplicate location "/images/". A modifier does not create a separate block — ^~ /x/ and /x/ are the same location. The lab below reproduces this error if you write both.

The lab

Edit the configuration and the path as you like. The algorithm running in your browser is a reimplementation of the five steps above, and it was checked against the real nginx 1.27.5 on exactly the 14 paths in the table — 14 out of 14 agree.

The location blocks

Request path to test

The order nginx works in

    Three ways this goes wrong

    Check yourself

    A config has location /api/ and location ~ \.json$. Which block serves /api/data.json? The regex block. The prefix /api/ is remembered at step 2, but it does not carry ^~, so step 4 still runs and the regex matches. To make the prefix win, write location ^~ /api/.
    Why does /img/b.txt reach the prefix block while /img/a.gif reaches a regex? Both have /img/ as their longest prefix, but step 4 runs before step 5. For .txt no regex matches, so the fallback happens; for .gif a regex matches and wins outright.
    With the same config, which block serves /img/a.GIF? Block 6, the /img/ prefix. Blocks 3 and 4 use ~ and are therefore case-sensitive, so an uppercase extension does not match; only block 5 uses ~*, and it looks for .PNG. This is a measurement, not a deduction.
    What happens if you add location ^~ /img/ to the configuration in the table? nginx will not start: location /img/ already exists, and since a modifier does not create a separate block, this is a duplicate location. Try it in the lab above.