How to Force WWW Redirect with SSL in Nginx Configuration


2 views

When configuring Nginx for production, one common requirement is enforcing consistent domain access - either always with WWW or always without. Here's a complete solution that handles both HTTP to HTTPS and non-WWW to WWW redirects.

# HTTP - non-WWW to HTTPS WWW
server {
    listen 80;
    server_name example.com;
    return 301 https://www.example.com$request_uri;
}

# HTTP - WWW to HTTPS WWW
server {
    listen 80;
    server_name www.example.com;
    return 301 https://www.example.com$request_uri;
}

# HTTPS - non-WWW to WWW
server {
    listen 443 ssl;
    server_name example.com;
    ssl_certificate /path/to/cert.pem;
    ssl_certificate_key /path/to/key.pem;
    return 301 https://www.example.com$request_uri;
}

# Main HTTPS WWW server block
server {
    listen 443 ssl;
    server_name www.example.com;
    
    ssl_certificate /path/to/cert.pem;
    ssl_certificate_key /path/to/key.pem;
    
    root /var/www/html;
    index index.php index.html;
    
    # Your regular configuration here
    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }
    
    # PHP handling if needed
    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php7.4-fpm.sock;
    }
}

The configuration handles all possible cases:

  • http://example.com → https://www.example.com
  • http://www.example.com → https://www.example.com
  • https://example.com → https://www.example.com

1. Missing return 301: Using rewrite instead of return for redirects is less efficient

2. SSL certificate mismatch: Ensure your certificate covers both www and non-www versions

3. Infinite redirect loops: Test all possible URL combinations

Before applying changes, always test with:

sudo nginx -t

Then verify all redirect scenarios using curl:

curl -I http://example.com
curl -I http://www.example.com
curl -I https://example.com

Using separate server blocks for redirects is more efficient than conditional rewrites within a single block. The 301 redirects are cached by browsers, reducing subsequent redirect overhead.

For high-traffic sites, consider adding these headers to your main server block:

add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload";
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;

When implementing redirects in Nginx, we often encounter edge cases where different URL patterns don't consistently redirect to the desired www-prefixed HTTPS version. The core issue manifests when:

  • Bare domain (site.com) redirects correctly to https://www.site.com
  • Subpages (site.com/index.php) redirect only to http://www.site.com (missing HTTPS)

Here's the proper way to handle both www and HTTPS enforcement:

# HTTP to HTTPS + WWW redirect for bare domain
server {
    listen 80;
    server_name site.com;
    return 301 https://www.site.com$request_uri;
}

# HTTP to HTTPS + WWW redirect for www domain
server {
    listen 80;
    server_name www.site.com;
    return 301 https://www.site.com$request_uri;
}

# HTTPS configuration
server {
    listen 443 ssl;
    server_name www.site.com;
    
    ssl_certificate /path/to/cert.pem;
    ssl_certificate_key /path/to/key.pem;
    
    root /home/site/public_html;
    index index.php;
    
    # Additional SSL config...
}

# Catch-all for HTTPS requests to bare domain
server {
    listen 443 ssl;
    server_name site.com;
    return 301 https://www.site.com$request_uri;
}

1. The $request_uri Variable

Using $request_uri instead of $1 is crucial because it:

  • Preserves the original request path
  • Includes query strings if present
  • Is more reliable than regex captures

2. Separate Server Blocks

Having distinct server blocks for each scenario ensures:

1. HTTP → HTTPS
2. non-WWW → WWW
3. All combinations handled explicitly

After implementing, verify with:

curl -I http://site.com
curl -I http://www.site.com
curl -I https://site.com
curl -I https://www.site.com/test?param=value

All should return 301 redirects to https://www.site.com with the path and query string preserved.

  • Mixing rewrite and return directives (stick with return 301)
  • Forgetting the HTTPS server block for the bare domain
  • Not testing with various URL patterns