SSL handshake failed
Published 15 September 2026
Half a dozen unrelated systems emit these three words, and each one means
something narrower than the others. Some write TLS handshake
failed instead: SSL is the old name for TLS, and the failure is the
same one. Which software wrote the line narrows
the cause more than anything else you can do, so start there rather than
with a generic checklist.
nginx: SSL_do_handshake() failed
In error.log, usually with an OpenSSL reason in brackets. The
reason code is the whole message; the words around it are boilerplate.
no shared cipher: yourssl_cipherslist and the client have nothing in common. Common after hardening a config against an old client.unsupported protocol: the client offered only TLS versions yourssl_protocolsline excludes.http request: someone sent plain HTTP to an HTTPS port. Not a TLS fault at all; usually a health check or a misconfigured upstream.certificate verify failed: only appears whenssl_verify_clientis on, and means the client's certificate was rejected.
Note that nginx logs these for inbound connections. If the failing
direction is nginx talking to an upstream, look for
proxy_ssl_* settings instead: a common miss is
proxy_ssl_server_name on;, without which nginx sends no SNI to
the backend.
Java: SSLHandshakeException
Java keeps its own trust store, entirely separate from the operating system's. A certificate the rest of the machine trusts perfectly well can still fail here, which is why "it works in curl" is not evidence of anything.
PKIX path building failed: the chain does not reach a root incacerts. Either the server sends an incomplete chain, or the root is genuinely absent, which is normal for an internal CA.No appropriate protocol: usually TLS 1.0/1.1 disabled injava.securitywhile the server still requires one.Received fatal alert: handshake_failure: the alert came from the far end, so the cause is over there.
Run with -Djavax.net.debug=ssl:handshake. It is verbose and it
is decisive: you see exactly which step failed rather than guessing.
Cloudflare error 525
A specific and frequently misread case. 525 means Cloudflare could not complete a handshake with your origin server: the visitor's connection to Cloudflare was fine. Nothing is wrong between the browser and the edge.
Almost always one of: no certificate installed on the origin at all, an expired origin certificate, the origin not accepting connections on 443, or SSL mode set to Full (Strict) with a self-signed certificate at the origin. Test the origin directly by IP, bypassing Cloudflare, and the real error appears immediately.
AWS load balancers
An ALB reports handshake failures in access logs and in the
ClientTLSNegotiationErrorCount metric. The usual cause is a
security policy that no longer permits what old clients offer: AWS retires
policies periodically, and a policy chosen years ago may have been replaced
underneath you.
Because the metric counts without explaining, it is worth enabling access logs before changing anything: the negotiated protocol and cipher appear per request, and the pattern of which clients fail tells you whether it is a protocol or a cipher problem.
curl, wget, Python and Go
These are client-side reports of a server-side refusal, and they share one
set of causes. GnuTLS-based builds phrase it as
peer failed to
perform TLS handshake, which is worked through in detail there: six causes in likelihood order, including the two that are easy to miss:
the server asking for a client certificate, and a path MTU black hole
swallowing the server's Certificate message.
The command that works regardless of source
openssl s_client -connect example.com:443 -servername example.com
Whatever wrote the log line, this tells you what the server does when a known-good client asks. If OpenSSL succeeds and your application fails, the difference is in your application's TLS settings or its trust store, which is exactly the situation Java produces most often. If OpenSSL fails too, the problem is the server or the path.
That default test does not prove the connection used post-quantum key agreement. If the failure appeared after a TLS library upgrade or only affects a larger ClientHello, test the negotiated group explicitly; X25519MLKEM768 explains the hybrid exchange and the middlebox failure pattern it can expose.
A failure that appears only when SNI privacy is required belongs to a different branch. Test the DNS HTTPS record and require negotiation as described in the Encrypted Client Hello test; an ordinary successful request may have silently fallen back without ECH.
Check the server side without installing anything. The Kodura SSL checker reports which TLS versions a host accepts, the negotiated cipher, and the chain exactly as sent: with no silent repairs. That answers the protocol and cipher questions above in one step, from outside your network.
The one thing worth not doing
Every source above has a documented way to accept weaker protocols, and it will make the error stop. It is almost never the right change: you are widening what a server accepts from everyone, to accommodate one client that should be updated instead. If the failing client genuinely cannot be updated (embedded hardware is the honest case) scope the exception to that path rather than the whole listener.