Nginx Location Matching
location blocks match request URIs. The matching rules are not “first one wins” — they have a specific priority that confuses everyone the first time. This is one of the top interview gotchas.
The five forms
| Form | Syntax | Example |
|---|---|---|
| Exact match | location = /path |
location = /favicon.ico |
| Prefix with stop | location ^~ /path |
location ^~ /static/ |
| Case-sensitive regex | location ~ pattern |
location ~ \.php$ |
| Case-insensitive regex | location ~* pattern |
location ~* \.(jpg|png)$ |
| Plain prefix | location /path |
location /api/ |
| Named location | location @name |
location @fallback (used by try_files, error_page) |
The matching algorithm — memorize this
Nginx evaluates locations in this order:
- Exact match (
=) — if alocation =matches, use it. Stop. - All prefix matches — find the longest matching prefix.
- If the longest match has
^~, use it. Stop. Skip regexes.
- If the longest match has
- Regex matches (
~and~*) — try each in the order they appear in config. First match wins. - If no regex matched — use the longest prefix from step 2.
So the priority is: = → ^~ longest prefix → regex (in file order) → plain prefix (longest).
Worked example
server {
location = / {
# Block A: exact match for "/"
}
location / {
# Block B: matches everything (catch-all prefix)
}
location /api/ {
# Block C: prefix
}
location ^~ /static/ {
# Block D: prefix with regex-skip
}
location ~ \.php$ {
# Block E: regex, .php files
}
location ~* \.(jpg|png|gif)$ {
# Block F: regex, image extensions case-insensitive
}
}
| Request URI | Matched block | Why |
|---|---|---|
/ |
A | exact match (=) wins |
/index.html |
B | no =, no ^~, no regex matches; longest prefix |
/api/users |
C | longer prefix than /; no regex matches |
/static/app.css |
D | ^~ matches, skip regex search |
/static/logo.png |
D | ^~ matches; without ^~, regex F would have won |
/script.php |
E | longest prefix is /; regex matches |
/photo.jpg |
F | longest prefix is /; regex matches |
The /static/logo.png case is the classic gotcha — without ^~, the file-extension regex would override your static-file location.
Prefix vs regex priority — the most common mistake
location /images/ {
root /var/www/cdn;
}
location ~ \.(jpg|png)$ {
root /var/www/wrong;
}
A request for /images/cat.jpg:
- Longest prefix:
/images/. - Regex
\.(jpg|png)$also matches. - Regex wins (no
^~). Served from/var/www/wrong.
Fix:
location ^~ /images/ {
root /var/www/cdn;
}
Now the prefix wins; regex isn’t tried.
= for hot exact matches
location = is the fastest match (single hash lookup). Use for high-volume exact endpoints:
location = / {
# homepage
}
location = /healthz {
return 200 "ok\n";
access_log off;
}
location = /favicon.ico {
log_not_found off;
access_log off;
}
These bypass the slower regex engine entirely.
Named locations
Named locations (@name) aren’t matched against URIs — they’re targets for internal redirects.
location / {
try_files $uri $uri/ @app;
}
location @app {
proxy_pass http://app_backend;
}
try_files checks the file paths in order; if none exist, it routes to @app.
Nested locations
location /api/ {
proxy_pass http://api;
location /api/admin/ {
allow 10.0.0.0/8;
deny all;
proxy_pass http://api;
}
}
Nested locations share the parent’s matched URI namespace. Used for “this subset of the path has different rules.”
root vs alias — quick reminder
(Comes up because they affect what URI maps to which file path):
location /static/ {
root /var/www; # serves /var/www/static/file.css
}
location /static/ {
alias /var/www/files/; # serves /var/www/files/file.css (no /static/ in path)
}
root appends the URI to the path. alias replaces the location prefix with the path. Trailing slash on alias matters; on root doesn’t.
Order of regex location blocks matters
Regex locations are tried in the order they appear:
location ~ \.(jpg|png)$ { ... } # block 1
location ~ ^/old/.+\.jpg$ { ... } # block 2 — never matched
Block 2 never wins because block 1 matches first. Reorder so the more-specific regex comes first.
Common interview confusions
- “First location block in the file wins.” — false. Exact >
^~longest prefix > regex in order > longest plain prefix. - “Regex always wins over prefix.” — only when the prefix is plain.
^~makes the prefix beat regex. - “
location /matches only/.” — it’s a prefix, matches everything. Uselocation = /for the exact root. - “Case in regex is automatic.” —
~is case-sensitive,~*is case-insensitive. Different operators.
Quick test: what matches what?
location = /a { return 200 "A\n"; }
location ^~ /b/ { return 200 "B\n"; }
location ~ \.py$ { return 200 "C\n"; }
location / { return 200 "D\n"; }
location /b/c/ { return 200 "E\n"; }
| URI | Result |
|---|---|
/a |
A (exact) |
/b/anything.py |
B (^~ skips regex) |
/b/c/x |
B (longest ^~ prefix; ^~ set on /b/, beats /b/c/’s plain prefix? No — ^~ requires being the longest prefix. /b/c/ is longer, so /b/c/ wins → E) |
Wait, let me redo that one:
/b/c/x:
- Longest prefix:
/b/c/(matches; longer than/b/). - That prefix doesn’t have
^~, so regex search runs. No regex matches/b/c/x. - Use longest plain prefix:
/b/c/→ E.
For /b/c/x.py:
- Longest prefix is still
/b/c/. - That prefix has no
^~, so regex runs.\.py$matches → C.
So the ^~ only protects /b/ itself; /b/c/ (longer) wins for paths under it. The lesson: ^~ doesn’t propagate to longer prefixes.
This is genuinely confusing. Read the matching rules carefully or run quick tests with return 200 "<label>\n"; blocks.
Interview angle
- “In what order does nginx evaluate
locationblocks?” — exact match (=), then longest prefix; if that prefix has^~, stop; otherwise try regexes in file order; first match wins; otherwise fall back to longest prefix. - “What’s the difference between
^~and~?” —^~is a prefix modifier meaning “if this prefix is the longest match, skip regex search.”~is a regex operator (case-sensitive). They serve different jobs. - “How do you make
/static/always serve from disk even though there’s a~ \.(jpg|png)$location?” — use^~ /static/so the prefix wins and regexes are skipped. - “Why might a request for
/foogo to a different location than you expect?” — most often: a regex location matches, overriding what looked like the obvious prefix match. Add^~to lock the prefix. - “
rootvsalias?” —rootappends the request URI to the path;aliasreplaces the location prefix. Trailing slash onaliasmatters. - “What’s a named location?” —
location @name, never matches a URI directly; used as a target fortry_filesanderror_pageinternal redirects.