Nginx HTTP to HTTPS Redirect with Subdomain Preservation: Complete Configuration Guide


4 views
# Base configuration for HTTP to HTTPS redirect
server {
    listen 80;
    server_name ~^(?.*)\.?example\.com$;
    
    # Preserve original request URI and protocol
    return 301 https://$subdomain.example.com$request_uri;
}

When implementing HTTP to HTTPS redirects in Nginx, developers often face the subdomain preservation issue. The naive approach using simple regex captures the path but loses valuable subdomain information. This becomes critical for:

  • Multi-tenant SaaS applications
  • Development/staging environments
  • Custom domain configurations

Here's the optimized configuration that handles all edge cases:

# Primary redirect configuration
server {
    listen 80 default_server;
    server_name _;
    
    # Dynamic capture of all possible subdomains
    if ($host ~* ^(www\.)?(?.+?)\.(?.+)$) {
        set $full_domain $subdomain.$domain;
    }
    
    # Special case for naked domain
    if ($host ~* ^(?[^/]+)$) {
        set $full_domain $domain;
    }
    
    return 301 https://$full_domain$request_uri;
}

# HTTPS server block (companion configuration)
server {
    listen 443 ssl;
    server_name ~^(?.*)\.?example\.com$;
    
    ssl_certificate /path/to/cert.pem;
    ssl_certificate_key /path/to/key.pem;
    
    # Your regular server configuration...
}

For complex deployments, consider these enhancements:

# Handle edge cases with map directive
map $host $redirect_host {
    default $host;
    "~*^www\.(.*)" $1;  # Strip www prefix if present
}

server {
    listen 80;
    server_name ~^(.*)\.?example\.com$;
    
    # Preserve query parameters
    return 301 https://$redirect_host$request_uri;
}

# Special case for HTTP/2 optimization
server {
    listen 443 ssl http2;
    server_name ~^(?.+)\.example\.com$;
    
    # HSTS header for security
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    
    # ... other SSL configurations
}

Verify your configuration works with these test cases:

  1. http://example.com → https://example.com
  2. http://app.example.com → https://app.example.com
  3. http://dev.app.example.com → https://dev.app.example.com
  4. http://example.com/path?query=1 → https://example.com/path?query=1

Use this curl command to validate redirects:

curl -I http://sub.example.com/some/path
  • Redirect loops: Ensure your SSL server block isn't accidentally listening on port 80
  • Wildcard certificates: Verify your SSL cert covers all subdomains
  • Performance impact: Use return 301 instead of rewrite for better efficiency

When implementing HTTP-to-HTTPS redirects in Nginx, a common approach is:

server {
    listen      80;
    server_name mysite.com;

    location / {
      rewrite     ^(.*)   https://mysite.com$1 permanent;
    }
}

This works fine for the main domain, but has a critical flaw - it strips away subdomain information. Requests to node1.mysite.com get redirected to https://mysite.com, losing the node1 subdomain.

The proper way to maintain subdomains is to use Nginx's $host variable, which contains the original hostname from the request:

server {
    listen      80;
    server_name ~^(.*)\.?mysite\.com$;

    return 301 https://$host$request_uri;
}

Key improvements:

  • $host preserves the original hostname (including subdomains)
  • $request_uri preserves the full original URI including query parameters
  • Using return 301 is more efficient than rewrite for simple redirects
  • The regex server_name pattern matches all subdomains

For sites with dynamic subdomains, you might want to use a wildcard SSL certificate and handle all possible subdomains:

server {
    listen      80;
    server_name ~^(.+)\.mysite\.com$ mysite.com;

    if ($host ~* ^(.*)\.mysite\.com$) {
        return 301 https://$host$request_uri;
    }
    
    return 301 https://mysite.com$request_uri;
}

For high-traffic sites, consider these optimizations:

server {
    listen      80 reuseport;
    server_name ~^(.*)\.?mysite\.com$;
    
    # Cache redirects for 1 hour
    add_header Cache-Control "public, max-age=3600";
    
    return 301 https://$host$request_uri;
}

After making changes, always:

  1. Test with nginx -t
  2. Reload with nginx -s reload
  3. Verify with curl: curl -I http://test.mysite.com

The response should show a 301 redirect to the HTTPS version with the subdomain intact.