Nginx location priority
If Nginx serves the wrong file, wrong headers, or wrong cache policy, the reason is often that a different location was selected than expected.
Knowing the selection order helps.
Main location types
location = /exact { ... }
location ^~ /assets/ { ... }
location /prefix/ { ... }
location ~ \.php$ { ... }
location / { ... }
Simplified selection order
location = /exact— exact match.- Longest prefix match.
- If that prefix used
^~, regex locations are skipped. - Regex locations
~and~*. - Normal prefix fallback, such as
location /.
Exact match
location = /404.html {
internal;
}
Exact location is useful for a single file or endpoint.
It matches only:
/404.html
Prefix match
location /assets/ {
...
}
Matches:
/assets/app.js
/assets/style.css
^~ prefix
location ^~ /assets/ {
...
}
This means: if the path starts with /assets/, select this location and do not check regex locations.
Useful when assets must not be intercepted by regex rules.
Regex location
location ~* \.(css|js)$ {
...
}
Regex is flexible, but it can intercept files unexpectedly.
For a simple static site, explicit prefix locations are often easier to reason about.
Typical static setup
location ^~ /assets/ {
try_files $uri =404;
add_header Cache-Control "public, max-age=31536000, immutable" always;
}
location = /404.html {
internal;
}
location / {
try_files $uri $uri/ =404;
add_header Cache-Control "public, must-revalidate" always;
}
Why put /assets/ above generic rules
Order is not always the deciding factor, but configs are easier to read when specific rules are above the general fallback.
Good order:
- exact service files;
- assets;
- special static paths;
- generic
location /.
How to see which location matched
A simple method is to temporarily add a debug header:
add_header X-Debug-Location "assets" always;
Then check:
curl -kI https://getsrv.app/assets/example.js | grep -i x-debug-location
Remove the debug header after testing.
Common mistakes
Mistake 1. Regex intercepts assets
Example:
location ~* \.(js|css|html)$ {
add_header Cache-Control "public, must-revalidate" always;
}
JS/CSS may receive HTML cache policy instead of immutable asset policy.
Mistake 2. Generic location is too clever
location / {
try_files $uri /index.html;
}
This can hide real 404 responses.
Mistake 3. add_header is not inherited
If a location defines its own add_header, parent add_header directives may not apply.
That is why important locations should be checked with curl -I.
Mistake 4. Multiple server blocks for one server_name
Check:
sudo grep -RIn "server_name getsrv.app" /etc/nginx/sites-enabled /etc/nginx/conf.d
If the same domain is defined in multiple places, you may be reading the wrong config.
Minimal check
curl -kI https://getsrv.app/
curl -kI https://getsrv.app/en/docs/
curl -kI https://getsrv.app/no-such-page
ASSET="$(find /var/www/getsrv.app/assets -maxdepth 1 -type f | head -n 1 | sed 's#^/var/www/getsrv.app##')"
curl -kI "https://getsrv.app$ASSET"
Check:
- status code;
content-type;cache-control;- security headers.
If HTML and assets have different expected headers, location routing is working correctly.