# 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. 1.Exact match (= /path) - Stops searching immediately
  2. 2.Priority prefix (^~ /path) - Stops searching if matched
  3. 3.Regex (~ /path or ~* /path) - Checked in order of appearance
  4. 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:

ModifierBehaviorExample
NonePrefix, longest match, continues searchinglocation /api
=Exact match only, stops searchinglocation = /api
^~Prefix, stops searching (no regex check)location ^~ /images
~Case-sensitive regex, order matterslocation ~ \.php$
~*Case-insensitive regex, order matterslocation ~* \.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

nginx
location /old {
    rewrite ^/old/(.*)$ /new/$1 break;
    # Continues in /old location with modified URI
}

Verification Steps

  1. 1.Dump all locations:
  2. 2.```bash
  3. 3.sudo nginx -T | grep -A 20 "location"
  4. 4.`
  5. 5.Test each path:
  6. 6.```bash
  7. 7.curl -I http://localhost/path/to/test
  8. 8.curl -I http://localhost/api
  9. 9.curl -I http://localhost/api/users
  10. 10.`
  11. 11.Add diagnostic headers:
  12. 12.```nginx
  13. 13.location /test1 {
  14. 14.add_header X-Location "test1";
  15. 15.}

location /test2 { add_header X-Location "test2"; } ```

  1. 1.Use debug log (if compiled with debug):
  2. 2.```nginx
  3. 3.error_log /var/log/nginx/debug.log debug;
  4. 4.`
  5. 5.Trace location matching:
  6. 6.```nginx
  7. 7.log_format location '$uri -> matched location [$time_local]';
  8. 8.access_log /var/log/nginx/location.log location;
  9. 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

ModifierPriorityStops SearchUse Case
=HighestYesExact single path
`^~HighYesPriority prefix (block regex)
~MediumFirst matchCase-sensitive regex
~*MediumFirst matchCase-insensitive regex
NoneLowestNo (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.