How to Force HTTPS with mod_rewrite Including SSL Termination Behind Proxies and Load Balancers


4 views

When dealing with modern web infrastructure, we often encounter situations where SSL termination happens at load balancers or reverse proxies. This creates a unique challenge for enforcing HTTPS at the application server level, as the traffic arrives as HTTP with special headers indicating the original protocol.

The core requirement is to:

  • Allow direct HTTPS connections
  • Allow HTTP connections that originated as HTTPS (identified by X-Forwarded-Proto header)
  • Redirect all other HTTP traffic to HTTPS

Here's the corrected and optimized mod_rewrite configuration:

RewriteEngine On
RewriteCond %{HTTPS} !=on [NC]
RewriteCond %{HTTP:X-Forwarded-Proto} !https [NC]
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]

The working version makes these crucial corrections:

  1. Uses proper header syntax HTTP:X-Forwarded-Proto instead of HTTP:http_x_forwarded_proto
  2. Added [NC] (no-case) flags for case-insensitive matching
  3. Simplified the condition checks by using negation operator (!)

For more complex environments, consider adding these variations:

# Handle cloudflare and other CDNs
RewriteCond %{HTTP:CF-Visitor} '"scheme":"http"'
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]

# Alternative for AWS ALB
RewriteCond %{HTTP:X-Forwarded-Port} !443
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]

When implementing these rules:

  • Place them early in your .htaccess file
  • Consider implementing at the vhost level for better performance
  • Test with R=302 first before making permanent R=301 redirects

Verify your setup works correctly by testing these scenarios:

curl -I http://yourdomain.com
curl -I -H "X-Forwarded-Proto: https" http://yourdomain.com
curl -I https://yourdomain.com

When implementing HTTPS redirection on servers behind SSL-terminating load balancers, developers often encounter a tricky situation. The traffic reaches your Apache server as HTTP (port 80) even though the original request was HTTPS. This happens because the load balancer handles the SSL termination and forwards requests unencrypted.

A typical HTTPS redirection rule would look like:


# Standard HTTPS redirect (won't work behind proxy)
RewriteEngine On
RewriteCond %{HTTPS} !=on
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]

This fails in proxied environments because %{HTTPS} will never be "on" when the SSL is terminated at the load balancer.

The working solution needs to check both direct HTTPS connections AND the X-Forwarded-Proto header:


# Proper HTTPS redirect handling both direct and proxied traffic
RewriteEngine On
RewriteCond %{HTTPS} !=on
RewriteCond %{HTTP:X-Forwarded-Proto} !https [NC]
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]

Key improvements in this version:

  • Uses HTTP:X-Forwarded-Proto instead of incorrect http_x_forwarded_proto
  • Added [NC] flag for case-insensitive matching
  • Simplified condition by removing redundant HTTPS check

For more complex setups, consider these variations:

1. Environment-Specific Configuration


<IfModule mod_rewrite.c>
    RewriteEngine On
    
    # Detect if we're behind a load balancer
    RewriteCond %{HTTP:X-Forwarded-Proto} ^https$ [OR]
    RewriteCond %{HTTPS} =on
    RewriteRule ^ - [S=1]
    
    # Redirect if neither condition matched
    RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
</IfModule>

2. Cloud-Specific Headers

Some cloud providers use different headers:


# AWS ALB/ELB specific
RewriteCond %{HTTP:X-Forwarded-Proto} !https
RewriteCond %{HTTP:CloudFront-Forwarded-Proto} !https
RewriteCond %{HTTP:CF-Visitor} !.*https.*
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]

When implementing these rules:

  • Use 301 (permanent) redirects for production
  • Place these rules early in your configuration
  • Consider adding [NE] flag if you have encoded URIs
  • Test with curl -v to verify headers are properly handled

If you encounter problems:

  1. Verify mod_rewrite is enabled (a2enmod rewrite)
  2. Check your Apache error logs for exact syntax errors
  3. Use RewriteLog and RewriteLogLevel for debugging
  4. Ensure your load balancer is actually setting the X-Forwarded-Proto header

Remember that:

  • The X-Forwarded-Proto header can be spoofed
  • Configure your load balancer to strip external X-Forwarded-Proto headers
  • Consider using mod_remoteip for IP-based validation