What's Actually Happening
Apache SSL handshake fails when the server and client cannot establish a secure TLS connection. This happens due to certificate issues, protocol mismatches, cipher incompatibility, or configuration errors.
The Error You'll See
Browser error:
SSL connection error
ERR_SSL_PROTOCOL_ERROR
Unable to establish secure connectioncurl error:
```bash $ curl -I https://example.com/
curl: (35) SSL connect error OpenSSL SSL_connect: SSL_ERROR_SYSCALL ```
Apache error log:
```bash $ tail /var/log/apache2/error.log
[error] SSL Library Error: 336031742 error:1407609C:SSL routines:SSL23_GET_CLIENT_HELLO:HTTP request [error] SSL handshake failed: server sent HTTP response to client expecting TLS ```
openssl test:
```bash $ openssl s_client -connect example.com:443
CONNECTED(00000003) 140737353915072:error:1408F10B:SSL routines:ssl3_get_record:wrong version number no peer certificate available ```
Why This Happens
- 1.Certificate expired - SSL certificate no longer valid
- 2.Certificate not matching - Certificate doesn't match hostname
- 3.Protocol mismatch - Client requires TLS version not enabled
- 4.Cipher mismatch - Client cipher not supported by server
- 5.Certificate chain incomplete - Missing intermediate certificates
- 6.Mixed HTTP/HTTPS - HTTP response on HTTPS port
Step 1: Check SSL Certificate
```bash # Check certificate file exists ls -la /etc/apache2/ssl/example.com.crt ls -la /etc/apache2/ssl/example.com.key
# Check certificate validity openssl x509 -in /etc/apache2/ssl/example.com.crt -text -noout | grep -A 2 Validity
# Check certificate expiration openssl x509 -in /etc/apache2/ssl/example.com.crt -noout -enddate # Output: notAfter=Apr 16 00:12:00 2026 GMT
# Check certificate matches hostname openssl x509 -in /etc/apache2/ssl/example.com.crt -noout -subject # Should contain: CN = example.com
# Verify certificate and key match openssl x509 -noout -modulus -in /etc/apache2/ssl/example.com.crt | openssl md5 openssl rsa -noout -modulus -in /etc/apache2/ssl/example.com.key | openssl md5 # MD5 hashes should match
# Check certificate chain openssl verify -CAfile /etc/apache2/ssl/ca-bundle.crt /etc/apache2/ssl/example.com.crt ```
Step 2: Check Apache SSL Configuration
```bash # View SSL configuration cat /etc/apache2/sites-enabled/example.com.conf
# Check SSL is enabled apache2ctl -M | grep ssl # Should show: ssl_module (shared)
# Enable SSL module if not loaded a2enmod ssl
# Check SSL virtual host configuration <VirtualHost *:443> ServerName example.com DocumentRoot /var/www/html
SSLEngine on SSLCertificateFile /etc/apache2/ssl/example.com.crt SSLCertificateKeyFile /etc/apache2/ssl/example.com.key SSLCertificateChainFile /etc/apache2/ssl/chain.crt
# Check these paths are correct </VirtualHost>
# Test configuration syntax apache2ctl configtest ```
Step 3: Fix Certificate Chain
```bash # Common issue: Missing intermediate certificate
# Check if chain file exists ls -la /etc/apache2/ssl/chain.crt
# If missing, download intermediate from CA # For Let's Encrypt: cat /etc/letsencrypt/live/example.com/chain.pem > /etc/apache2/ssl/chain.crt
# For other CAs, download from certificate authority
# Combine certificate files if needed cat example.com.crt intermediate.crt > combined.crt
# Update Apache configuration SSLCertificateFile /etc/apache2/ssl/combined.crt # Or SSLCertificateChainFile /etc/apache2/ssl/chain.crt
# Reload Apache systemctl reload apache2
# Verify chain openssl s_client -connect example.com:443 -showcerts # Should show complete chain ```
Step 4: Enable Correct TLS Protocols
```bash # Check current SSL protocol settings cat /etc/apache2/mods-enabled/ssl.conf | grep SSLProtocol
# Enable modern TLS versions # In ssl.conf or virtual host: SSLProtocol all -SSLv2 -SSLv3 -TLSv1 -TLSv1.1 # Only TLSv1.2 and TLSv1.3 enabled
# Or specify explicitly: SSLProtocol TLSv1.2 TLSv1.3
# Check client requirements # Some clients may require specific TLS version
# Test SSL connection with specific protocol openssl s_client -connect example.com:443 -tls1_2 openssl s_client -connect example.com:443 -tls1_3
# If protocol not supported, error shows # Enable the required protocol in Apache ```
Step 5: Configure Cipher Suites
```bash # Check current cipher configuration cat /etc/apache2/mods-enabled/ssl.conf | grep SSLCipherSuite
# Set recommended cipher suites SSLCipherSuite ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384
# Or use high security cipher suite SSLCipherSuite HIGH:!aNULL:!MD5:!3DES
# Cipher order preference SSLHonorCipherOrder on
# Check supported ciphers openssl ciphers -v 'HIGH:!aNULL'
# Test specific cipher openssl s_client -connect example.com:443 -cipher 'ECDHE-RSA-AES128-GCM-SHA256'
# Reload after changes systemctl reload apache2 ```
Step 6: Fix HTTP on HTTPS Port
```bash # Error: SSL routines:SSL23_GET_CLIENT_HELLO:HTTP request # Means HTTP response sent on HTTPS port
# Check if virtual host has correct port <VirtualHost *:443> # HTTPS port SSLEngine on # SSL enabled # ... </VirtualHost>
# Check for conflicting HTTP virtual host on 443 # WRONG: <VirtualHost *:443> # SSLEngine off or missing # HTTP content </VirtualHost>
# CORRECT: <VirtualHost *:80> # HTTP content </VirtualHost>
<VirtualHost *:443> SSLEngine on # HTTPS content </VirtualHost>
# Ensure each port has correct configuration apache2ctl -S # Shows virtual host port mappings ```
Step 7: Update Expired Certificate
```bash # If certificate expired, renew it
# Check expiration openssl x509 -in /etc/apache2/ssl/example.com.crt -noout -enddate # Output shows expiration date
# For Let's Encrypt, renew: certbot renew
# Manual renewal certbot certonly --apache -d example.com
# Update paths in Apache SSLCertificateFile /etc/letsencrypt/live/example.com/fullchain.pem SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem
# Reload Apache systemctl reload apache2
# For commercial certificates: # 1. Generate CSR openssl req -new -newkey rsa:2048 -nodes -keyout example.com.key -out example.com.csr
# 2. Submit CSR to CA # 3. Download new certificate # 4. Install certificate # 5. Reload Apache ```
Step 8: Test SSL Configuration
```bash # Test SSL connection openssl s_client -connect example.com:443 -servername example.com
# Check SSL details openssl s_client -connect example.com:443 -showcerts | openssl x509 -noout -text
# Test with curl curl -vI https://example.com/
# Use SSL testing tools # SSL Labs: https://www.ssllabs.com/ssltest/
# Check SSL configuration best practices apachectl -t -D DUMP_MODULES | grep ssl
# Verify certificate chain openssl verify -CAfile /etc/ssl/certs/ca-certificates.crt /etc/apache2/ssl/example.com.crt ```
Step 9: Enable SSL Logging
```bash # Enable SSL debug logging # In Apache configuration: LogLevel ssl:debug
# Or for specific module: LogLevel ssl_module:debug
# Reload Apache systemctl reload apache2
# Check detailed SSL logs tail -f /var/log/apache2/error.log | grep SSL
# After debugging, restore normal level LogLevel ssl:warn
# SSL handshake details logged # Helps identify specific failure point ```
Step 10: Use SSL Configuration Tools
```bash # Mozilla SSL Configuration Generator # https://ssl-config.mozilla.org/
# Recommended modern configuration: <VirtualHost *:443> SSLEngine on SSLCertificateFile /path/to/cert.pem SSLCertificateKeyFile /path/to/key.pem
# Modern configuration (Mozilla intermediate) SSLProtocol all -SSLv2 -SSLv3 -TLSv1 -TLSv1.1 SSLCipherSuite ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384 SSLHonorCipherOrder off SSLSessionTickets off
# OCSP Stapling SSLUseStapling On SSLStaplingCache "shmcb:logs/ssl_stapling(32768)"
# Security headers Header always set Strict-Transport-Security "max-age=63072000" </VirtualHost>
# Test configuration apache2ctl configtest && systemctl reload apache2 ```
SSL Handshake Failure Checklist
| Check | Command | Expected |
|---|---|---|
| Certificate valid | openssl x509 -enddate | not expired |
| Certificate matches | openssl x509 -subject | CN=hostname |
| Chain complete | openssl s_client | full chain |
| TLS 1.2 enabled | SSLProtocol | TLSv1.2 |
| Ciphers match | openssl ciphers | supported |
| HTTP/HTTPS correct | apachectl -S | correct ports |
Verify the Fix
```bash # After fixing SSL configuration
# 1. Test SSL connection openssl s_client -connect example.com:443 -servername example.com # Should show: Verify return code: 0 (ok)
# 2. Test with curl curl -I https://example.com/ # Should return HTTP 200, not SSL error
# 3. Check certificate details openssl s_client -connect example.com:443 | openssl x509 -noout -dates # Should show valid dates
# 4. Verify protocol support openssl s_client -connect example.com:443 -tls1_2 # Should connect successfully
# 5. Check Apache error log tail /var/log/apache2/error.log # Should not show SSL errors
# 6. Test in browser # Navigate to https://example.com # Should load without SSL errors
# 7. SSL Labs test # https://www.ssllabs.com/ssltest/ # Should show A or A+ rating ```
Related Issues
- [Fix Apache SSL Certificate Not Found](/articles/fix-apache-ssl-certificate-not-found)
- [Fix Apache HTTPS Redirect Loop](/articles/fix-apache-https-redirect-loop)
- [Fix Apache Mixed Content Warning](/articles/fix-apache-mixed-content-warning)