Nginx Nested Location Blocks: Why Regex OR Patterns Fail and How to Refactor Shared Configurations


3 views

When attempting to nest prefix-based location blocks inside a regex location block with alternation (the | operator), Nginx throws a specific error because of how location matching works internally. The error message "location \"/a\" is outside location \"/a|/b\"" reveals a fundamental constraint in Nginx's configuration parsing.

Nginx processes location blocks in a specific order:

1. Exact match locations (=) first
2. Prefix-based locations (/) next
3. Regex locations (~) last

The key problem is that nested locations must be strictly contained within their parent location's match pattern. When using ~ /a|/b, the regex doesn't create a proper containment relationship for the individual prefix locations.

Here are three production-tested approaches:

Option 1: Using Include Directives

location /a {
    include shared-config.conf;
    # a-specific directives
}

location /b {
    include shared-config.conf;
    # b-specific directives
}

Option 2: Map-Based Routing

map $uri $route {
    ~^/a  'a';
    ~^/b  'b';
}

server {
    location / {
        # Shared config
        proxy_set_header X-Custom-Header "value";
        
        # Route-specific configs
        if ($route = 'a') {
            # a-specific config
        }
        if ($route = 'b') {
            # b-specific config
        }
    }
}

Option 3: Prefix Capture Groups

location ~ ^/(a|b) {
    # Shared config
    set $prefix $1;
    
    location ~ ^/a/ {
        # a-specific config
    }
    
    location ~ ^/b/ {
        # b-specific config
    }
}

Each approach has different performance characteristics:

  • Include files are parsed at configuration load
  • Map variables add minimal runtime overhead
  • Nested regex locations have matching overhead

For high-traffic sites, benchmark with nginx -T and stub_status to measure impact.

Here's how we implemented shared CORS headers across multiple endpoints:

# cors-shared.conf
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';

# main config
location /api/v1/users {
    include cors-shared.conf;
    proxy_pass http://user_service;
}

location /api/v1/products {
    include cors-shared.conf;
    proxy_pass http://product_service;
}

When troubleshooting location blocks:

  1. Use nginx -T to test configuration
  2. Enable debug logging with error_log /var/log/nginx/error.log debug;
  3. Check evaluation order with curl -v requests

When working with Nginx configuration, you might encounter a puzzling error when trying to nest location blocks with regex patterns:

nginx: [emerg] location "/a" is outside location "/a|/b" in /etc/nginx/nginx.conf:36

This occurs specifically when attempting to use regex alternation (|) in the parent location block while trying to nest exact match locations.

Nginx processes location blocks in a specific order and has limitations when combining different matching types:

  • Regex locations (~) and prefix locations have different matching behaviors
  • Nginx doesn't support nesting exact match locations inside regex locations with alternation
  • The error suggests Nginx can't properly scope the nested locations within the regex pattern

Here are several approaches to achieve your goal without repetition:

Option 1: Using Common Configuration with Includes

# Define common configuration in a separate file
# common.conf
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
# ... other common directives

# Main configuration
location /a {
    include common.conf;
    # specific directives for /a
}

location /b {
    include common.conf;
    # specific directives for /b
}

Option 2: Using Regex Capture Groups

location ~ ^/(a|b) {
    # Common directives
    proxy_pass http://backend;
    
    # Specific handling based on capture
    if ($1 = "a") {
        # /a specific configuration
    }
    if ($1 = "b") {
        # /b specific configuration
    }
}

Option 3: Map Directive for Complex Routing

map $uri $backend {
    ~^/a  backend_a;
    ~^/b  backend_b;
}

server {
    location / {
        proxy_pass http://$backend;
        # Common configuration here
    }
}

When structuring Nginx configurations:

  • Prefer prefix matches (location /path/) over regex when possible
  • Use include directives to avoid configuration duplication
  • Consider using map for complex routing logic
  • Test configuration changes with nginx -t before reloading

Remember that:

  • Regex locations are evaluated sequentially until a match is found
  • Exact match locations (=) and prefix locations have higher priority
  • Nested locations can impact performance if overused