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:

bash
SSL connection error
ERR_SSL_PROTOCOL_ERROR
Unable to establish secure connection

curl 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. 1.Certificate expired - SSL certificate no longer valid
  2. 2.Certificate not matching - Certificate doesn't match hostname
  3. 3.Protocol mismatch - Client requires TLS version not enabled
  4. 4.Cipher mismatch - Client cipher not supported by server
  5. 5.Certificate chain incomplete - Missing intermediate certificates
  6. 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

CheckCommandExpected
Certificate validopenssl x509 -enddatenot expired
Certificate matchesopenssl x509 -subjectCN=hostname
Chain completeopenssl s_clientfull chain
TLS 1.2 enabledSSLProtocolTLSv1.2
Ciphers matchopenssl cipherssupported
HTTP/HTTPS correctapachectl -Scorrect 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 ```

  • [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)