Introduction
SSL handshake failures in HAProxy occur when the load balancer cannot establish a secure TLS connection with either the client (frontend) or the backend server. These failures manifest as connection resets, timeout errors, or "handshake failure" alerts in logs. The issue can stem from certificate problems, protocol mismatches, cipher incompatibilities, or configuration errors.
Symptoms
Error messages in HAProxy logs:
SSL handshake failure: no shared cipher
SSL handshake failure: unsupported protocol
SSL handshake failure: certificate verify failed
TLS handshake failure: connection resetClient-facing symptoms:
- Browser shows "SSL_ERROR_NO_CYPHER_OVERLAP" or "ERR_SSL_VERSION_OR_CIPHER_MISMATCH"
- curl fails with "SSL certificate problem: unable to get local issuer certificate"
- Intermittent failures with some clients but not others
- Health checks failing with SSL backends
Common Causes
- 1.Certificate chain incomplete - Missing intermediate certificates
- 2.Protocol version mismatch - Server requires TLS 1.2+ but client only supports TLS 1.0
- 3.Cipher suite mismatch - No overlapping ciphers between client and server
- 4.Certificate expired or not yet valid - Time-based certificate issues
- 5.SNI not configured - Server requires SNI but HAProxy not sending it
- 6.CA certificate missing - Backend verification fails without proper CA bundle
Step-by-Step Fix
Step 1: Check Certificate Configuration
```bash # List loaded certificates echo "show ssl cert" | socat stdio /var/run/haproxy.sock
# Verify certificate file openssl x509 -in /etc/ssl/certs/server.crt -text -noout
# Check certificate chain openssl verify -CAfile /etc/ssl/certs/ca-bundle.crt /etc/ssl/certs/server.crt
# Verify certificate dates openssl x509 -in /etc/ssl/certs/server.crt -noout -dates ```
Step 2: Test SSL Connection
```bash # Test from client to HAProxy openssl s_client -connect haproxy.example.com:443 -servername haproxy.example.com
# Test from HAProxy to backend openssl s_client -connect backend.example.com:8443 -servername backend.example.com
# Check specific protocols openssl s_client -connect backend:8443 -tls1_2 openssl s_client -connect backend:8443 -tls1_3 ```
Step 3: Fix Frontend SSL Termination
```haproxy frontend https_front bind *:443 ssl crt /etc/ssl/certs/server.pem alpn h2,http/1.1
# Proper bind configuration with certificate chain bind *:443 ssl crt /etc/ssl/certs/fullchain.pem \ ca-file /etc/ssl/certs/ca-bundle.crt \ verify optional
# Modern TLS configuration ssl-default-bind-options ssl-min-ver TLSv1.2 ssl-default-bind-ciphersuites TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256 ssl-default-bind-ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384 ```
Step 4: Fix Backend SSL Connection
```haproxy backend secure_backend balance roundrobin
# Enable SSL for backend connections option httpchk GET /health HTTPS
server web1 10.0.0.1:8443 ssl verify required \ ca-file /etc/ssl/certs/ca-bundle.crt \ alpn h2,http/1.1 \ sni str(backend.example.com)
# Or disable verification for testing (not recommended for production) server web2 10.0.0.2:8443 ssl verify none ```
Step 5: Configure Proper Certificate Bundle
```bash # Create full chain certificate (server + intermediate) cat /etc/ssl/certs/server.crt /etc/ssl/certs/intermediate.crt > /etc/ssl/certs/fullchain.pem
# Include private key in PEM for HAProxy cat /etc/ssl/private/server.key /etc/ssl/certs/fullchain.pem > /etc/ssl/certs/server.pem
# Verify the PEM file openssl verify -CAfile /etc/ssl/certs/ca-bundle.crt /etc/ssl/certs/server.pem ```
Step 6: Reload and Verify
```bash # Check configuration haproxy -c -f /etc/haproxy/haproxy.cfg
# Graceful reload systemctl reload haproxy
# Test the SSL connection curl -v --tlsv1.2 https://haproxy.example.com/ curl -v --tlsv1.3 https://haproxy.example.com/ ```
Advanced Diagnosis
Debug SSL Handshake
```bash # Enable SSL debug logging haproxy -d -f /etc/haproxy/haproxy.cfg
# Check OCSP stapling echo "show ssl ocsp-response" | socat stdio /var/run/haproxy.sock
# Show TLS tickets echo "show ssl tlskeys" | socat stdio /var/run/haproxy.sock ```
Check Protocol and Cipher Support
```bash # Test all protocols for proto in tls1 tls1_1 tls1_2 tls1_3; do echo "Testing $proto:" openssl s_client -connect backend:443 -$proto 2>&1 | grep "Protocol|Cipher" done
# List supported ciphers nmap --script ssl-enum-ciphers -p 443 backend.example.com ```
Verify SNI Configuration
```haproxy # Frontend SNI routing frontend https_front bind *:443
# Route based on SNI acl is_api ssl_fc_sni api.example.com use_backend api_backend if is_api
# Pass SNI to backend default_backend web_backend
backend web_backend # Use server name indication http-request set-header Host backend.example.com server web1 10.0.0.1:443 ssl sni str(backend.example.com) ```
Common Pitfalls
- Missing intermediate certificate - Certificate chain incomplete in PEM file
- Wrong certificate file format - Need combined key + cert for HAProxy bind
- TLS 1.3 cipher syntax - Ciphersuites vs ciphers syntax confusion
- Backend verification disabled - Using
verify nonein production - Expired CA bundle - Old CA certificates not updated after rotation
- SNI missing on backend - Modern servers require SNI for proper routing
Best Practices
```haproxy # Modern SSL configuration defaults ssl-default-bind-options ssl-min-ver TLSv1.2 no-sslv3 no-tlsv10 no-tlsv11 ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384 ssl-default-bind-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256 ssl-default-server-options ssl-min-ver TLSv1.2 ssl-default-server-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384
frontend https bind *:443 ssl crt /etc/ssl/certs/fullchain.pem alpn h2,http/1.1 http-response set-header Strict-Transport-Security "max-age=31536000; includeSubDomains"
backend secure_api server api1 10.0.0.1:443 ssl verify required ca-file /etc/ssl/certs/ca-bundle.crt sni str(api.internal) ```
Related Issues
- HAProxy Backend Down
- HAProxy Connection Refused to Backend
- Traefik SSL Certificate Error
- Nginx Load Balancer Timeout