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


2 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/