Deploy Error Decoder

SSL handshake failed — diagnose and fix certificate errors

Quick answer: An SSL/TLS handshake failure means the client and server couldn't establish a secure connection. The usual causes are an expired certificate, a missing intermediate chain, a hostname that doesn't match the cert, or a protocol/cipher mismatch. Diagnose it precisely with openssl s_client -connect host:443 -servername host — it shows the chain, expiry, and exactly where the handshake breaks.

What the error looks like

It surfaces differently per client, but all mean the secure channel never formed:

curl: (35) error:0A000126:SSL routines::unexpected eof
curl: (60) SSL certificate problem: unable to get local issuer certificate

# browser:
ERR_SSL_PROTOCOL_ERROR

# node:
Error: certificate has expired   (code: CERT_HAS_EXPIRED)

The handshake is where the cert, chain, hostname, and protocol are all checked. Each failure mode has a distinct fingerprint — so read the exact message rather than guessing.

Why it happens

Expired certificate

The cert's validity window has passed. The fix is renewal, often an automation that lapsed.

Missing intermediate chain

The server sends only the leaf cert; clients can't build a path to a trusted root.

Hostname mismatch

The cert's SAN doesn't include the hostname being requested.

Protocol or cipher mismatch

The client requires a TLS version or cipher the server disabled (e.g. old TLS 1.0/1.1).

Diagnose it in three steps

1

Inspect the live handshake

openssl s_client -connect example.com:443 -servername example.com
# shows the cert chain, 'Verify return code', and where it stops
2

Check expiry and chain

echo | openssl s_client -connect example.com:443 2>/dev/null \
  | openssl x509 -noout -dates -subject -issuer
3

Confirm hostname and protocol

# Does the SAN list include the host? Is TLS 1.2/1.3 offered?
openssl s_client -connect example.com:443 -tls1_2
The real fix

Serve the full chain, valid and matched

Most handshake failures come down to the certificate you serve. Use the full-chain file (leaf + intermediates), keep renewal automated so it never expires, make sure the hostname is in the SAN, and offer modern TLS:

# nginx: serve fullchain (not just the leaf cert)
ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
How Infraveil handles this

You own TLS — we sign what we send

Your proxy and certificates are yours to run — Infraveil doesn't terminate your TLS. What it does, on your own servers, is sign and verify the traffic between its agent and the control plane (the agent even verifies Infraveil's own signatures), so the channel it controls is provably authentic. Trust by inspection, not assertion.

You own the proxy, certs, and TLS termination — we don't host your app
Agent ↔ control-plane traffic is signed and verified, not just encrypted
Every action recorded in a tamper-evident trail you can inspect

Frequently asked questions

What causes an SSL handshake to fail?

The client and server couldn't agree on a secure connection — commonly an expired certificate, a missing intermediate chain, a hostname that doesn't match the cert, or a TLS version/cipher mismatch.

How do I debug a TLS handshake?

Use openssl s_client -connect host:443 -servername host to see the certificate chain, validity dates, and the verify return code that pinpoints the failure.

What is a missing intermediate certificate?

The server sent only its leaf certificate, so clients can't build a trust path to a root CA. Serve the full chain (fullchain.pem) including intermediates.

Why does it work in my browser but fail in curl or Node?

Browsers sometimes cache intermediates from prior sites, masking a missing chain. curl and Node don't, so they fail — which means the server really is misconfigured. Serve the full chain.