When transitioning from HTTPS back to HTTP, many developers encounter a frustrating chicken-and-egg problem: the server attempts to establish an SSL connection before processing redirect rules. This becomes particularly problematic when:
- The SSL certificate has expired
- The certificate has been revoked
- You've completely removed SSL configuration
The typical mod_rewrite solution:
RewriteEngine On
RewriteCond %{HTTPS} on
RewriteRule ^(.*)$ http://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
fails because Apache processes SSL negotiation before evaluating .htaccess rules. The SSL handshake occurs at the protocol level before mod_rewrite gets involved.
For Apache servers, you need to implement the redirect at the virtual host level:
<VirtualHost *:443>
ServerName example.com
Redirect permanent / http://example.com/
# Optional error logging
ErrorLog ${APACHE_LOG_DIR}/ssl_redirect_error.log
CustomLog ${APACHE_LOG_DIR}/ssl_redirect_access.log combined
</VirtualHost>
If you still need some SSL functionality while redirecting most traffic:
<IfModule mod_ssl.c>
<VirtualHost *:443>
ServerName example.com
SSLEngine on
# Use a self-signed cert if no valid one exists
SSLCertificateFile /path/to/dummy.crt
SSLCertificateKeyFile /path/to/dummy.key
Redirect permanent / http://example.com/
</VirtualHost>
</IfModule>
For those using Nginx, the solution is more straightforward:
server {
listen 443 ssl;
server_name example.com;
ssl_certificate /path/to/dummy.crt;
ssl_certificate_key /path/to/dummy.key;
return 301 http://$host$request_uri;
}
When some resources must remain HTTPS while redirecting the main site:
<VirtualHost *:443>
ServerName example.com
SSLEngine on
# ... SSL configuration ...
# Redirect only specific paths
RewriteEngine On
RewriteCond %{REQUEST_URI} !^/secure-path
RewriteRule ^(.*)$ http://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
</VirtualHost>
Implement caching headers to prevent repeated SSL attempts:
<VirtualHost *:443>
Header always set Cache-Control "no-store, must-revalidate"
Header always set Pragma "no-cache"
Header always set Expires "0"
</VirtualHost>
When transitioning from HTTPS to HTTP, we face a fundamental protocol limitation: the SSL/TLS handshake occurs before any HTTP traffic (including redirects) can be processed. This creates a chicken-and-egg problem where browser warnings appear before our redirect rules can execute.
The typical mod_rewrite approach:
RewriteEngine On
RewriteCond %{HTTPS} on
RewriteRule ^(.*)$ http://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
fails because Apache processes these rules after establishing the SSL connection. With an invalid/expired certificate, the connection never reaches this stage.
Option 1: Apache VirtualHost Configuration
For Apache servers, configure this in your main server config (not .htaccess):
<VirtualHost *:443>
ServerName example.com
Redirect permanent / http://example.com/
SSLEngine off # Critical for bypassing cert check
</VirtualHost>
Option 2: Nginx Pre-SSL Redirect
For Nginx, use this server block:
server {
listen 443 ssl;
server_name example.com;
ssl_certificate /dev/null; # Dummy path
ssl_certificate_key /dev/null;
return 301 http://$host$request_uri;
}
If you don't have server config access:
- Use Cloudflare's "Always Use HTTP" option
- Configure AWS ALB to terminate SSL and forward HTTP
- Implement a reverse proxy with redirect logic
For legacy browsers that cache certificate errors, add this meta tag to any error pages:
<meta http-equiv="refresh" content="0;url=http://example.com">
Verify with curl to bypass browser caching:
curl -vIk https://example.com --connect-to ::80
Look for "301 Moved Permanently" in headers.
Remember to update all canonical tags and internal links to prevent mixed content issues post-migration.