# Nginx Location Block Priority Issues
Requests go to unexpected location blocks. /api/users matches /api instead of /api/users. Regex locations override prefix locations when they shouldn't. Understanding Nginx's location matching priority is essential for correct routing, and the order isn't always intuitive.
Understanding Location Matching Priority
Nginx evaluates location blocks in a specific order:
- 1.Exact match (
= /path) - Stops searching immediately - 2.Priority prefix (
^~ /path) - Stops searching if matched - 3.Regex (
~ /pathor~* /path) - Checked in order of appearance - 4.Prefix (
/path) - Longest match wins, but continues searching for regex
Key insight: Prefix locations don't stop searching - Nginx continues looking for regex matches unless ^~ is used.
Check which location handles a request:
``nginx
add_header X-Location-Matched $uri always;
Common Cause 1: Regex Overrides Prefix
Prefix location matched first, but regex overrides it.
Problematic config: ```nginx location /images/ { root /var/www/images; }
location ~ \.jpg$ { root /var/www/photos; # Regex wins, even though /images/ matched first } ```
Request /images/photo.jpg goes to regex location.
Solution: Use priority prefix: ```nginx location ^~ /images/ { # Stops searching - regex never checked root /var/www/images; }
location ~ \.jpg$ { root /var/www/photos; # Only matches .jpg outside /images/ } ```
Common Cause 2: Exact Match Not Used
When you need one specific path to have different handling.
Problematic config: ```nginx location / { root /var/www/html; }
location /api { proxy_pass http://backend; } ```
Request /api matches /api, but /api/something also matches /api.
Solution: Use exact match: ```nginx location = /api { # Only matches /api exactly, not /api/users return 200 '{"status": "ok"}'; }
location /api/ { # Matches /api/users, /api/data, etc. proxy_pass http://backend; }
location / { root /var/www/html; } ```
Common Cause 3: Longest Prefix Not Matching
Prefix matching favors the longest match.
Problematic config: ```nginx location /api { proxy_pass http://api-backend; }
location /api/v2 { proxy_pass http://api-v2-backend; } ```
Request /api/v2/users matches /api/v2 (longer prefix wins).
Request /api/users matches /api.
This is correct behavior. But if you expect /api to catch /api/v2/users:
Solution: Use exact or different structure: ```nginx location = /api { proxy_pass http://api-backend; }
location /api/v2 { proxy_pass http://api-v2-backend; }
location /api/ { # Only catches /api/other (not v2) proxy_pass http://api-backend; } ```
Common Cause 4: Regex Order Matters
Regex locations are checked in order of appearance, not specificity.
Problematic config: ```nginx location ~ ^/api { proxy_pass http://api-backend; }
location ~ ^/api/v2/users { proxy_pass http://users-backend; # Never matches - first regex catches it } ```
Request /api/v2/users matches first regex ^/api.
Solution: Order regex by specificity: ```nginx location ~ ^/api/v2/users { # More specific regex first proxy_pass http://users-backend; }
location ~ ^/api/v2 { proxy_pass http://api-v2-backend; }
location ~ ^/api { proxy_pass http://api-backend; } ```
Common Cause 5: Case Sensitivity Issues
Regex ~ is case-sensitive, ~* is case-insensitive.
Problematic config:
``nginx
location ~ \.php$ {
fastcgi_pass php:9000;
}
Request /file.PHP doesn't match (case-sensitive).
Solution: Use case-insensitive regex:
``nginx
location ~* \.php$ {
fastcgi_pass php:9000;
}
Common Cause 6: Nested Locations Not Working
Nginx doesn't truly nest locations - inheritance is limited.
Problematic config: ```nginx location /api { proxy_pass http://backend;
location /api/users { # This inherits from /api? No - it's standalone proxy_pass http://users-backend; # Overwrites parent proxy_pass } } ```
Nested locations can inherit some directives, but proxy_pass must be set explicitly.
Solution: Set all needed directives in nested location: ```nginx location /api { proxy_pass http://backend; proxy_set_header Host $host;
location /api/users { proxy_pass http://users-backend; proxy_set_header Host $host; # Must repeat } } ```
Common Cause 7: Location Modifier Confusion
Different modifiers behave differently.
Modifiers explained:
| Modifier | Behavior | Example |
|---|---|---|
| None | Prefix, longest match, continues searching | location /api |
= | Exact match only, stops searching | location = /api |
^~ | Prefix, stops searching (no regex check) | location ^~ /images |
~ | Case-sensitive regex, order matters | location ~ \.php$ |
~* | Case-insensitive regex, order matters | location ~* \.jpg$ |
Problem: Using wrong modifier: ```nginx location = /api/ { # Exact match - only matches "/api/" literally # NOT "/api/users" }
location ~ /api { # Regex - inefficient for simple prefix } ```
Solution: Use appropriate modifier: ```nginx location /api/ { # Prefix - matches /api/users, /api/data }
location = /api { # Exact - matches only /api }
location ^~ /static/ { # Priority prefix - stops regex search } ```
Common Cause 8: Trailing Slash Issues
Trailing slashes affect matching behavior.
Problematic config: ```nginx location /api { proxy_pass http://backend; # Request "/api" matches # Request "/api/" matches # Request "/api/users" matches }
location /api/ { proxy_pass http://backend/api/; # Request "/api" DOES NOT match (no trailing slash) # Request "/api/" matches # Request "/api/users" matches } ```
Test trailing slash behavior:
``bash
curl http://localhost/api # Hits /api location
curl http://localhost/api/ # Hits /api location (if no /api/ exists)
curl http://localhost/api/users # Hits /api location
Solution: Be consistent with slashes: ```nginx location /api/ { proxy_pass http://backend/api/; # Ensure both have trailing slash for consistent behavior }
# Or handle both: location /api { rewrite ^/api$ /api/ permanent; }
location /api/ { proxy_pass http://backend/api/; } ```
Common Cause 9: Location Rewrite Interference
Rewrites change the URI before location matching.
Problematic config: ```nginx location /old { rewrite ^/old/(.*)$ /new/$1 last; }
location /new { proxy_pass http://backend; } ```
rewrite ... last restarts location matching with new URI.
Solution: Understand rewrite flags:
- last - Stops rewrite processing, restarts location matching
- break - Stops rewrite processing, continues in current location
- redirect - Returns 302 temporary redirect
- permanent - Returns 301 permanent redirect
location /old {
rewrite ^/old/(.*)$ /new/$1 break;
# Continues in /old location with modified URI
}Verification Steps
- 1.Dump all locations:
- 2.```bash
- 3.sudo nginx -T | grep -A 20 "location"
- 4.
` - 5.Test each path:
- 6.```bash
- 7.curl -I http://localhost/path/to/test
- 8.curl -I http://localhost/api
- 9.curl -I http://localhost/api/users
- 10.
` - 11.Add diagnostic headers:
- 12.```nginx
- 13.location /test1 {
- 14.add_header X-Location "test1";
- 15.}
location /test2 { add_header X-Location "test2"; } ```
- 1.Use debug log (if compiled with debug):
- 2.```nginx
- 3.error_log /var/log/nginx/debug.log debug;
- 4.
` - 5.Trace location matching:
- 6.```nginx
- 7.log_format location '$uri -> matched location [$time_local]';
- 8.access_log /var/log/nginx/location.log location;
- 9.
`
Complete Working Configuration
```nginx server { listen 80; server_name example.com;
# Exact match for specific paths location = / { return 200 "Homepage exact match"; }
location = /favicon.ico { return 204; }
# Priority prefix - stop regex search for static files location ^~ /static/ { root /var/www; expires 1y; }
location ^~ /images/ { root /var/www; expires 30d; }
# Regex for specific file types (outside ^~ paths) location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ { root /var/www; expires 30d; }
# API routing - specific first location ^~ /api/v2/ { proxy_pass http://api-v2-backend/; }
location ^~ /api/ { proxy_pass http://api-backend/; }
# PHP handling location ~ \.php$ { fastcgi_pass php:9000; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; }
# Default fallback location / { root /var/www/html; try_files $uri $uri/ =404; } } ```
Quick Reference
| Modifier | Priority | Stops Search | Use Case |
|---|---|---|---|
= | Highest | Yes | Exact single path |
| `^~ | High | Yes | Priority prefix (block regex) |
~ | Medium | First match | Case-sensitive regex |
~* | Medium | First match | Case-insensitive regex |
| None | Lowest | No (continues to regex) | Standard prefix |
Location matching follows a strict priority order. Use = for exact matches, ^~ to block regex interference, and order regex locations by specificity.