MonitoringIntermediate

Fix SSL Handshake Failed Errors

SSL handshake failed means client and server could not agree on a secure connection. The causes on each side and how to diagnose them in minutes.

ObserveOne Team
4 min read

"SSL handshake failed" means the client and server started negotiating a secure connection and gave up partway: no agreed protocol version, no agreed cipher, a certificate one side refused, or a message one side never received. It is the umbrella diagnosis; the fix depends on which handshake step died. Cloudflare users see the same failure with a number on it: error 525.

What the Handshake Does#

In one round trip (TLS 1.3) the two sides agree on a protocol version and cipher suite, the server proves its identity with its certificate, and both derive the session keys. Each step is a distinct failure point:

StepFailureTypical evidence
Version negotiationNo shared TLS versionprotocol version alert in openssl
Cipher negotiationNo shared cipher suitehandshake failure alert immediately after ClientHello
Certificate validationExpired, wrong name, broken chain, bad key usageVerify return code non-zero
SNI routingServer picks wrong vhost/cert for the nameWrong certificate in openssl output
Transport interferenceProxy, firewall, or MTU eats handshake messagesConnection hangs or resets mid-handshake

Client-Side Causes#

When one machine fails and others work: wrong system clock (certificate dates appear invalid), antivirus or corporate proxies intercepting TLS, an outdated OS or browser without modern protocol support, or a VPN mangling packets. The fix list is the same as for ERR_SSL_PROTOCOL_ERROR: clock, interception software, updates, then network.

Server-Side Causes#

When everyone fails:

  • Legacy protocol config: only TLS 1.0/1.1 enabled; modern clients walk away. Fix per the cipher mismatch guide.
  • Certificate problems: expired, name mismatch, or an incomplete chain; the handshake reaches verification and dies there.
  • SNI misrouting: multi-tenant servers that present the default vhost's certificate when the requested name has no explicit config.
  • Mutual TLS surprises: a server demanding client certificates fails every client that does not have one; common after enabling mTLS on the wrong listener.
  • Proxy chain disagreements: with a CDN in front, the edge-to-origin leg has its own handshake; Cloudflare's 525 means specifically that leg failed, so the fix belongs on the origin.

What the Error Codes Actually Say#

"Handshake failed" is the umbrella message; the specific error underneath it depends on the client. Node.js and most other TLS stacks surface the underlying OpenSSL X.509 verify code directly:

OpenSSL verify codeMeaningTypical cause
UNABLE_TO_VERIFY_LEAF_SIGNATUREUnable to verify the first certificateBroken or incomplete chain
UNABLE_TO_GET_ISSUER_CERT_LOCALLYUnable to get local issuer certificateClient is missing the intermediate/root CA
DEPTH_ZERO_SELF_SIGNED_CERTSelf-signed certificateServer presents a self-signed cert to a client expecting a CA-signed one
SELF_SIGNED_CERT_IN_CHAINSelf-signed certificate in the chainAn untrusted root sits in the served chain
CERT_HAS_EXPIREDCertificate has expiredRenewal missed
HOSTNAME_MISMATCHHostname mismatchCertificate's SAN/CN doesn't match the requested domain

curl reports the same underlying failures in its own wording (SSL certificate problem: unable to get local issuer certificate, certificate has expired), and browsers translate them into their own error pages rather than the raw OpenSSL string. Whatever the client, the verify code from openssl s_client (below) is the same code the client library is reacting to, so it is the fastest way to name the failure precisely instead of guessing from a vague client-side message.

Diagnose in Two Commands#

curl -v https://example.com 2>&1 | grep -E "SSL|TLS|certificate"
openssl s_client -connect example.com:443 -servername example.com

curl tells you whether and where the handshake fails; openssl shows the negotiated protocol, the certificate presented, the chain served, and the verify code. Between them you can name the failing step in under a minute, then apply the step-specific fix above.

Limitations to Know#

  • "Handshake failed" is a symptom, not a diagnosis. Logs that record only this string force you to re-derive the failing step every incident; capture the alert detail or verify code where you can.
  • Intermittent failures usually mean a pool. One bad backend behind a load balancer fails a fraction of handshakes while every manual test passes; test each backend directly.

Conclusion#

A failed handshake is one of five steps breaking: version, cipher, certificate, SNI, or the transport between the parties. Localize client-vs-server first, let openssl name the step, and the fix is the standard one for that step.

Handshake health is also invisible from inside a working session, which is why these failures run quietly for hours. ObserveOne performs a full fresh handshake from outside on every scheduled check, so the first one that fails becomes an alert instead of a trend in your bounce rate.

Ready for AI-Powered Testing?

ObserveOne monitors your selectors 24/7 and automatically heals them when websites change. Never deal with broken tests again.

Start Free Trial