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
- Is there a
= /pathblock that matches exactly? If so it wins immediately and nothing else is considered. - Find the longest matching prefix block, including blocks carrying
^~. Remember it; do not use it yet. - Does that longest prefix carry
^~? If so, use it and skip every regex. - Try the regexes in the order they appear in the file. The first one that matches wins.
- 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"; }
| Path | Winning block | Why |
|---|---|---|
/exact | 1 exact | exact match, step 1 |
/exact/ | 9 default | an exact match is byte-for-byte; one extra / is a different URI |
/images/a.gif | 2 prefix-priority | ^~ blocks the regexes at step 3, even though block 3 also matches |
/images/b.txt | 2 prefix-priority | same block; here no regex matches anyway, so the two results coincide |
/img/a.gif | 3 regex-gif-jpg | the earlier regex wins; block 4 is narrower but never runs |
/img/a.GIF | 6 prefix-img | blocks 3 and 4 both use ~, which is case-sensitive, so the request drops to step 5 |
/img/b.txt | 6 prefix-img | no regex matches, so back to the prefix at step 5 |
/photo.gif | 3 regex-gif-jpg | the regex matches |
/photo.PNG | 5 regex-png-ci | ~* is case-insensitive |
/photo.png | 5 regex-png-ci | block 3 only covers gif and jpg |
/doc | 7 prefix-doc | the prefix matches |
/document | 8 prefix-document | the longer prefix wins |
/documentation | 8 prefix-document | still the longest prefix |
/other | 9 default | only / 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
-
A narrow regex written after a broad one. In the table above, block 4
(
^/img/.*\.gif$) never runs because block 3 (\.(gif|jpg)$) matches first. To make block 4 effective, it has to be moved above block 3. -
Using
=for a path that may carry a trailing slash.= /exactdoes not catch/exact/. If you need both, write two blocks or use a prefix. -
Reading
^~as "high priority". It only means "if this prefix turns out to be the longest one, stop before the regexes". A different, longer prefix still beats it.
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.