How to Implement Subdomain-Specific Basic Auth in Nginx Location Blocks


1 views

When running multiple domains under a single Nginx server block, developers often need to apply different security rules to specific subdomains. The standard location directive doesn't directly support hostname matching, which creates this common pain point.

# This WON'T work as expected:
location x.domain.com {
    auth_basic "Admin Login";
    auth_basic_user_file /etc/nginx/.htpasswd;
}

The location directive interprets "x.domain.com" as a URI path rather than a hostname match.

Option 1: Separate Server Blocks

server {
    listen 80;
    server_name x.domain.com;
    
    location / {
        auth_basic "Admin Login";
        auth_basic_user_file /etc/nginx/.htpasswd;
    }
}

server {
    listen 80;
    server_name www.domain.com y.domain.com;
    
    # Normal configuration here
}

Option 2: Conditional Map-Based Approach

map $http_host $auth_required {
    default 0;
    "x.domain.com" 1;
}

server {
    listen 80;
    server_name www.domain.com x.domain.com y.domain.com;
    
    location / {
        if ($auth_required) {
            auth_basic "Admin Login";
            auth_basic_user_file /etc/nginx/.htpasswd;
        }
    }
}

Option 3: Using the $host Variable

server {
    listen 80;
    server_name www.domain.com x.domain.com y.domain.com;
    
    location / {
        if ($host = x.domain.com) {
            auth_basic "Admin Login";
            auth_basic_user_file /etc/nginx/.htpasswd;
        }
    }
}

While all three solutions work, the separate server block approach (Option 1) generally performs best for production environments because:

  • No conditional logic evaluation at runtime
  • Cleaner configuration separation
  • Easier to maintain and debug

For more complex scenarios with pattern-based subdomains:

server {
    listen 80;
    server_name ~^(?.+)\.domain\.com$;
    
    location / {
        if ($subdomain = "x") {
            auth_basic "Admin Login";
            auth_basic_user_file /etc/nginx/.htpasswd;
        }
    }
}

Always validate your Nginx config changes:

sudo nginx -t
sudo systemctl reload nginx

Test with curl to verify:

curl -I http://x.domain.com
# Should return 401 Unauthorized

When running multiple domains under a single Nginx server block, developers often need to apply specific configurations (like basic authentication) to individual subdomains. The standard location directive doesn't natively support hostname matching, which creates this common pain point.

server {
    listen 80;
    server_name www.domain.com x.domain.com y.domain.com;
    
    # This affects ALL domains in server_name
    auth_basic "Admin Login";
    auth_basic_user_file /etc/nginx/.htpasswd;
}

The above approach applies basic auth to all listed domains, which isn't our desired outcome.

Nginx provides several variables we can leverage for host-specific routing:

  • $host: Contains the current request's host header
  • $http_host: Similar but includes port if present
  • $server_name: The matched server_name from config

Here's the proper way to implement subdomain-specific authentication:

server {
    listen 80;
    server_name www.domain.com x.domain.com y.domain.com;
    
    location / {
        if ($host = "x.domain.com") {
            auth_basic "Admin Login";
            auth_basic_user_file /etc/nginx/.htpasswd;
        }
        
        # Other configuration directives
        try_files $uri $uri/ =404;
    }
}

For more complex scenarios with multiple protected subdomains:

map $host $is_protected {
    default 0;
    "x.domain.com" 1;
    "admin.domain.com" 1;
}

server {
    # ... other config ...
    
    location / {
        if ($is_protected) {
            auth_basic "Restricted Area";
            auth_basic_user_file /etc/nginx/.htpasswd;
        }
    }
}
  • Always test regex patterns thoroughly - they're case-sensitive by default
  • Consider performance impact when using many if conditions
  • For production, consider separating domains into individual server blocks when possible

Add these to your config when troubleshooting:

add_header X-Host-Debug $host always;
add_header X-Request-Debug "$request" always;