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:
- Uses proper header syntax
HTTP:X-Forwarded-Proto
instead ofHTTP:http_x_forwarded_proto
- Added
[NC]
(no-case) flags for case-insensitive matching - 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 permanentR=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 incorrecthttp_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:
- Verify mod_rewrite is enabled (
a2enmod rewrite
) - Check your Apache error logs for exact syntax errors
- Use
RewriteLog
andRewriteLogLevel
for debugging - 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