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:
- Port 443 requests bypass the port 80 virtual host
- 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/