How to Fix 400 Bad Request Error When Accessing HTTPS Port via HTTP and Implement Proper Redirection


12 views

When users accidentally access port 443 (HTTPS default port) via HTTP protocol (http://domain.com:443), they receive a 400 Bad Request error instead of being redirected to the HTTPS version. The Apache server explicitly states: "You're speaking plain HTTP to an SSL-enabled server port."

The typical HTTPS redirection in <VirtualHost *:80> doesn't catch this case because:

  1. Port 443 requests bypass the port 80 virtual host
  2. SSL negotiation happens before HTTP layer processing
# This won't work for http:443 requests
RewriteEngine On 
RewriteCond %{HTTPS} off
RewriteRule (.*) https://%{SERVER_NAME}/$1 [R,L]

1. Create Dedicated Non-SSL VirtualHost

Add this configuration in Apache (e.g., /etc/apache2/sites-available/000-nonssl.conf):

<VirtualHost *:443>
    ServerName yourdomain.com
    ServerAlias www.yourdomain.com
    DocumentRoot /var/www/html
    
    ErrorLog ${APACHE_LOG_DIR}/error.log
    CustomLog ${APACHE_LOG_DIR}/access.log combined
    
    # Force HTTPS via redirect
    RewriteEngine On
    RewriteCond %{HTTP:X-Forwarded-Proto} !https
    RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
</VirtualHost>

2. Configure SSL VirtualHost Properly

Your SSL VirtualHost should include strict protocol settings:

<IfModule mod_ssl.c>
<VirtualHost *:443>
    ServerAdmin webmaster@localhost
    DocumentRoot /var/www/html
    
    SSLEngine on
    SSLCertificateFile /etc/letsencrypt/live/domain.com/fullchain.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/domain.com/privkey.pem
    
    # Security headers
    Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
    
    # Protocol handling
    SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1
    SSLCipherSuite EECDH+AESGCM:EDH+AESGCM
</VirtualHost>
</IfModule>

3. Handling Let's Encrypt Challenges

The connection errors during certbot validation indicate deeper configuration issues:

# First verify ports are open:
sudo netstat -tulpn | grep ':443'
sudo ufw status

# For certbot troubleshooting:
sudo certbot --apache --dry-run -d domain.com -d www.domain.com \
    --pre-hook "systemctl stop apache2" \
    --post-hook "systemctl start apache2"

For those using NGINX, the solution is more straightforward:

server {
    listen 443 ssl;
    listen [::]:443 ssl;
    server_name domain.com;
    
    # Catch HTTP requests on SSL port
    if ($scheme = http) {
        return 301 https://$server_name$request_uri;
    }
    
    # SSL configuration
    ssl_certificate /etc/letsencrypt/live/domain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/domain.com/privkey.pem;
    # ... rest of SSL config
}

After implementation, verify with:

curl -Iv http://domain.com:443  # Should show 301 redirect
openssl s_client -connect domain.com:443 -tls1_2 # Check SSL handshake
sudo apache2ctl configtest  # Validate config syntax

When users accidentally access your site via http://example.com:443, Apache throws a 400 Bad Request error instead of performing the desired HTTPS redirect. This occurs because:

"Reason: You're speaking plain HTTP to an SSL-enabled server port. Instead use the HTTPS scheme to access this URL"

The typical HTTPS redirect in 000-default.conf won't work here because:

RewriteEngine On 
RewriteCond %{HTTPS} off
RewriteRule (.*) https://%{SERVER_NAME}/$1 [R,L]

This only handles port 80 requests. Port 443 requests bypass this VirtualHost entirely.

Add this to your Apache configuration:

<VirtualHost *:443>
    ServerName example.com
    Redirect permanent / https://example.com/
    SSLEngine off
</VirtualHost>

For more control, modify your SSL VirtualHost:

<VirtualHost *:443>
    ServerName example.com
    SSLEngine on
    # Your SSL certificates here
    
    RewriteEngine On
    RewriteCond %{SERVER_PORT} =443
    RewriteCond %{HTTPS} =off
    RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
</VirtualHost>

The TLS-SNI-01 challenge failure suggests additional configuration needed:

<VirtualHost *:80>
    ServerName example.com
    DocumentRoot /var/www/html
    
    Alias /.well-known/acme-challenge/ /var/www/letsencrypt/.well-known/acme-challenge/
    <Directory /var/www/letsencrypt>
        Options None
        AllowOverride None
        Require all granted
    </Directory>
</VirtualHost>

Verify with these commands:

sudo apachectl configtest
sudo systemctl restart apache2
curl -I http://example.com:443

Should return:

HTTP/1.1 301 Moved Permanently
Location: https://example.com/