SSL Handshake Failed: Causes and Fixes
Quick reference
| Error messages | ERR_SSL_PROTOCOL_ERROR, SSL_ERROR_HANDSHAKE_FAILURE_ALERT, SSL: HANDSHAKE_FAILURE |
|---|---|
| Layer | TLS (after TCP connection, before HTTP) |
| HTTP code? | No — TLS layer failure |
| Cloudflare code | 525 SSL Handshake Failed |
What SSL handshake failure means
Every HTTPS connection begins with a TLS handshake — a negotiation phase where client and server agree on a TLS version, cipher suite, and exchange certificates and session keys. If any step of this negotiation fails, the handshake is aborted and no HTTP data is exchanged. The client sees a connection error rather than an HTTP status code.
The handshake has several stages, each of which can fail independently: ClientHello (client sends supported versions and ciphers), ServerHello (server selects version and cipher), Certificate (server sends its certificate), certificate validation (client verifies the certificate), and key exchange (both sides derive the session key). A failure at any stage produces an SSL handshake failed error, with different alert codes indicating where it failed.
TLS alert codes
TLS uses alert messages to communicate handshake failures. The alert code identifies the failure type:
| Alert | Meaning | Common cause |
|---|---|---|
| handshake_failure (40) | No mutually supported cipher/version | Protocol version mismatch or old cipher only |
| certificate_unknown (46) | Certificate could not be verified | Self-signed, expired, wrong CA |
| bad_certificate (42) | Certificate parsing failed | Malformed certificate file |
| unrecognized_name (112) | SNI hostname not recognized | Virtual host SNI misconfiguration |
| protocol_version (70) | Protocol version not supported | Client or server requires newer/older TLS |
Diagnosing the failure
Use openssl to identify the specific failure point:
openssl s_client -connect example.com:443 -servername example.com # Useful flags: openssl s_client -connect example.com:443 -tls1_2 # force TLS 1.2 openssl s_client -connect example.com:443 -tls1_3 # force TLS 1.3 openssl s_client -connect example.com:443 -ssl3 # test SSLv3 (should fail) # Show full cert chain: openssl s_client -connect example.com:443 -showcerts # Verbose cipher negotiation: openssl s_client -connect example.com:443 -cipher "ECDHE-RSA-AES128-GCM-SHA256"
Check what TLS versions and ciphers the server supports:
# Using nmap's ssl-enum-ciphers script: nmap --script ssl-enum-ciphers -p 443 example.com # Or sslyze (Python): pip install sslyze --break-system-packages python -m sslyze example.com
Check for SNI issues:
# With SNI (correct way): openssl s_client -connect IP:443 -servername example.com # Without SNI (wrong way — may get default/wrong certificate): openssl s_client -connect example.com:443 # Compare: do both return the same certificate?
Causes and fixes
No mutually supported TLS version. The client requires TLS 1.2+ but the server only supports TLS 1.0/1.1, or vice versa. Fix: update the server's TLS configuration to support TLS 1.2 and 1.3:
# nginx: ssl_protocols TLSv1.2 TLSv1.3; # Apache: SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1
No mutually supported cipher suite. Client and server share no cipher in common. Modern clients require ECDHE (forward secrecy) cipher suites. Old servers configured with RC4, 3DES, or NULL ciphers only will fail against modern clients:
# nginx — modern cipher configuration: ssl_ciphers 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; ssl_prefer_server_ciphers off;
Certificate issues triggering handshake abort. If the client receives a certificate it cannot validate (expired, self-signed without trust, wrong CA), it sends a certificate_unknown or bad_certificate alert and aborts. See the ERR_CERT_COMMON_NAME_INVALID guide for certificate-specific fixes.
SNI not configured. A server hosting multiple domains must use SNI to serve the correct certificate per domain. If the server does not support SNI or is not configured for it, all HTTPS requests get the same (possibly wrong) certificate. Ensure nginx has separate server blocks with ssl_certificate directives per domain, and that the server is compiled with SNI support (standard in all modern builds).
Mutual TLS (mTLS) client certificate missing. Some APIs and internal services require the client to present its own certificate. If the server expects a client certificate and the client does not send one, the handshake fails with certificate_required or handshake_failure. The client must be configured with a valid client certificate:
# curl with client certificate:
curl --cert client.pem --key client.key https://api.example.com/
# Python requests:
requests.get('https://api.example.com/', cert=('client.pem', 'client.key'))
Frequently asked questions
What is the difference between SSL and TLS?
SSL (Secure Sockets Layer) is the original protocol, with versions SSLv2 and SSLv3, both now deprecated and disabled. TLS (Transport Layer Security) is the successor — TLS 1.0, 1.1, 1.2, 1.3. Despite this, the industry still commonly says "SSL" when referring to TLS. All modern browsers and servers use TLS 1.2 or TLS 1.3. SSLv3 should be disabled everywhere.
Can antivirus or security software cause SSL handshake failures?
Yes. SSL inspection software (corporate proxies, antivirus tools that intercept HTTPS) performs a man-in-the-middle TLS termination. If the interception certificate is not trusted by the client, the handshake appears to fail. This is common in corporate environments where the IT-managed device trusts the inspection proxy certificate but personal devices do not.
How do I test if a specific cipher works?
Use openssl s_client with the -cipher flag: openssl s_client -connect example.com:443 -cipher "ECDHE-RSA-AES256-GCM-SHA384". A successful handshake output means the server supports that cipher. An error ("no ciphers available" or "SSL handshake failure") means it does not.
What is forward secrecy and why does it matter?
Forward secrecy means each TLS session uses a unique session key that is not derived from the server's private key. Even if the server's private key is later compromised, past recorded traffic cannot be decrypted. ECDHE key exchange provides forward secrecy. RSA key exchange (without ephemeral keys) does not. All modern TLS cipher suites use ECDHE.
Related guides
Cloudflare 525 · Cloudflare 526 · ERR_CERT_COMMON_NAME_INVALID · HTTP 101 · HTTP 426