What Causes SSL Handshake Failed
The handshake is the negotiation phase before TLS encryption starts — client and server exchange supported versions, cipher suites, and the server's certificate. When any of these steps fails to reach agreement, the connection is aborted before a single byte of application data moves, which is why the error page carries no HTTP status code.
Ranked by how often each shows up in support tickets and server logs:
- TLS version mismatch — server only accepts TLS 1.3, client is stuck on TLS 1.0/1.1 (or vice versa on a locked-down legacy client). Per RFC 8446 §4.1.4, a server that cannot negotiate a mutually supported version MUST abort with a
protocol_versionalert. - No shared cipher suite — client and server support overlapping TLS versions but zero common ciphers, usually after a server hardening pass disabled older suites. RFC 8446 §6.2 defines this as
handshake_failure(alert code 40) — the generic "could not negotiate parameters" alert. - Broken certificate chain — the leaf certificate is valid but the server isn't sending the intermediate CA certificate, so the client can't build a trust path to a root it already trusts.
- Expired or not-yet-valid certificate — server clock drift or an unrenewed cert triggers a
certificate_expiredalert (RFC 8446 §6.2, code 45). - SNI mismatch — the client didn't send (or the server didn't match) the
server_nameextension, so the server presented the wrong certificate for a multi-tenant host. - Client-side clock skew — if the client's system clock is off by more than a few minutes, a completely valid certificate is rejected as expired or not-yet-valid.
- Middlebox or antivirus interception — corporate proxies and some antivirus TLS-inspection features present their own certificate or strip supported cipher lists, breaking the handshake before it reaches the real server.
How the TLS Handshake Works
A standard TLS 1.3 handshake (per RFC 8446 §2) runs in this order:
- ClientHello — client sends supported TLS versions, cipher suites, and the
server_name(SNI) it's trying to reach. - ServerHello — server picks one TLS version and one cipher suite from the client's list, or aborts with
handshake_failure/protocol_versionif there's no overlap. - Certificate + CertificateVerify — server sends its certificate chain; client validates it against a trusted root, the hostname, and the validity window.
- Finished — both sides confirm the handshake transcript matches, and application data starts flowing.
"SSL handshake failed" specifically means step 2 or step 3 aborted — the connection never reached step 4.
TLS Alert Codes Reference Table
| Alert name | Code | Meaning | Typical trigger |
|---|---|---|---|
handshake_failure | 40 | No overlapping cipher suite/parameters | Hardened server, legacy client |
bad_certificate | 42 | Certificate is malformed or fails validation | Corrupted cert file |
unsupported_certificate | 43 | Certificate type not supported by client | Wrong key algorithm (e.g. Ed25519 on old client) |
certificate_expired | 45 | Cert notAfter date has passed, or clock is wrong | Unrenewed Let's Encrypt cert |
certificate_unknown | 46 | Client can't otherwise validate the cert | Broken intermediate chain |
unknown_ca | 48 | Client doesn't trust the issuing CA | Self-signed or private CA not in trust store |
decrypt_error | 51 | Signature or key exchange verification failed | Key/cert mismatch |
protocol_version | 70 | No shared TLS version | Client stuck on TLS 1.0/1.1, server requires 1.2+ |
insufficient_security | 71 | Server needs stronger ciphers than client offers | Outdated client cipher list |
The alert name in your browser console or server log (e.g. ssl_error_handshake_failure_alert) maps directly to one row here — that row tells you which side (version, cipher, or certificate) to fix first.
Diagnose the Exact Failure with OpenSSL
Run this against the failing host — it reproduces the handshake and prints the exact alert:
openssl s_client -connect example.com:443 -servername example.com -tls1_2 2>&1 | head -30
Read the output for these three signals:
CONNECTED(00000003)
140187654321:error:1409442E:SSL routines:ssl3_read_bytes:tlsv1 alert protocol version:...
alert protocol versionin the output → TLS version mismatch (cause #1 above).unable to get local issuer certificate→ broken chain (cause #3).Verify return code: 10 (certificate has expired)→ expired cert (cause #4).
To check which TLS versions the server actually offers:
for v in tls1 tls1_1 tls1_2 tls1_3; do
echo "-- $v --"
openssl s_client -connect example.com:443 -$v </dev/null 2>&1 | grep -E "Protocol|alert"
done
For a browser-side check without a terminal, run the SSL Checker — it reports the certificate chain, expiry, and supported protocols in one pass. For DNS-related SNI issues (wrong IP being contacted), confirm the hostname resolves correctly first with DNS Lookup.
Fix Per Root Cause
TLS version mismatch: Enable TLS 1.2 and 1.3 on the server, and only drop TLS 1.0/1.1 once you've confirmed no legacy clients depend on them. On nginx:
ssl_protocols TLSv1.2 TLSv1.3;
No shared cipher suite: Add a modern cipher list that still covers TLS 1.2 clients rather than restricting to TLS 1.3-only ciphers:
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384;
Broken certificate chain: Serve the full chain (leaf + intermediate), not just the leaf certificate:
cat leaf-cert.pem intermediate-cert.pem > fullchain.pem
Expired certificate: Renew it — for Let's Encrypt:
certbot renew --cert-name example.com && systemctl reload nginx
Client clock skew: Sync the client's clock — most handshake failures reported as "certificate expired" on an otherwise valid cert are this:
sudo ntpdate pool.ntp.org
SNI mismatch: Confirm the client is sending SNI (-servername flag in openssl tests) and that the server block matches the exact hostname requested, not just a wildcard default.
Verify the Fix
After applying a fix, re-run the SSL Checker at /ssl-checker to confirm the certificate chain resolves and the negotiated protocol is TLS 1.2 or 1.3. Also check response headers with HTTP Headers Check to confirm Strict-Transport-Security is present and the connection completes without a downgrade.
Stop checking manually. DNSnexus Monitor watches your SSL certificate expiry, chain validity, and protocol support — and alerts you before a handshake failure takes your site offline. Free tier covers one domain.
Key Takeaways
- ✓
handshake_failure(alert 40) andprotocol_version(alert 70) are the two most common SSL handshake failed causes — per RFC 8446 §6.2. - ✓
openssl s_client -connect host:443 -servername hostreproduces the failure and names the exact alert. - ✓ A broken certificate chain (missing intermediate) fails silently on some clients and not others — always serve the full chain.
- ✓ Client-side clock skew causes false "certificate expired" handshake failures even on valid certificates.
- ✓ Disable TLS 1.0/1.1 only after confirming no dependent clients — don't drop TLS 1.2 support.
Frequently Asked Questions
Q: What does SSL handshake failed mean exactly?
It means the client and server couldn't agree on a TLS version, cipher suite, or valid certificate during the negotiation phase, so no encrypted connection was ever established (RFC 8446 §2).
Q: Why does Chrome show ERR_SSL_VERSION_OR_CIPHER_MISMATCH?
Chrome shows this when the server doesn't support any TLS version or cipher suite the browser offers — most often because the server still requires TLS 1.0/1.1, which modern Chrome versions disable by default.
Q: How do I check what TLS versions my server supports?
Run openssl s_client -connect host:443 -tls1_2 (swap the flag for -tls1_3, etc.) and check the Protocol: line in the output, or use the SSL Checker for a browser-based report.
Q: Can a wrong system clock cause SSL handshake failed?
Yes — if the client or server clock is off by more than a few minutes, a valid certificate can be rejected as expired or not-yet-valid, triggering certificate_expired (alert 45).
Q: Is SSL handshake failed the same as ERR_SSL_PROTOCOL_ERROR?
No. ERR_SSL_PROTOCOL_ERROR is a broader Chrome error covering malformed TLS records; handshake failed specifically means version/cipher/certificate negotiation was rejected. See the ERR_SSL_PROTOCOL_ERROR guide for that distinction.
Q: Does a missing intermediate certificate always break the handshake?
It depends on the client — some browsers cache intermediates from prior visits and won't fail, while most mobile apps and API clients that don't cache will reject the connection. Always serve the full chain to avoid inconsistent results.
Q: How do I fix a handshake failure caused by an antivirus or proxy?
Temporarily disable HTTPS/TLS inspection in the antivirus or proxy settings and retest — if the handshake succeeds, the inspection feature is re-signing certificates in a way the client doesn't trust.
Next Steps
Run the SSL Checker to see your certificate chain, expiry, and supported protocols in one report, and confirm the hostname resolves correctly with DNS Lookup. If handshake failures keep recurring around certificate renewal, read ERR_SSL_PROTOCOL_ERROR: What It Is and How to Fix It and SSL Certificate Expiry Monitoring to catch expirations before they cause outages. For chain-of-trust issues specifically, CAA Records and SSL Explained covers how certificate authorities are authorized for your domain. If you're seeing a certificate error rather than a handshake failure, Common SSL Errors: NET::ERR_CERT_*, EXPIRED, UNTRUSTED maps every browser's exact error string to its root cause.