How to Force HTTPS to HTTP Redirect Before SSL Handshake Error in Apache


2 views

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.