How to Fix SSL/TLS Handshake Failure: Complete Debug Guide
Diagnose and fix SSL handshake failures. Debug protocol mismatches, cipher issues, cert chain problems, and SNI errors with openssl and curl.
An SSL/TLS handshake failure means the client and server could not agree on how to establish an encrypted session. The connection is aborted before any application data is exchanged. Because the failure can originate from either side and be triggered by protocols, ciphers, certificates, or clocks, methodical debugging is essential. This guide walks through the eight most common causes and shows how to isolate each with openssl, curl, and Wireshark.
Step 1: Capture the exact error
Start by reproducing the failure with verbose output. In a browser, open DevTools and check the Security tab; note the ERR_SSL_* code. From the command line, use:
curl -v https://example.com # add `--trace-ascii /dev/stderr` for byte-level detailLook for lines beginning with *. Common signals include SSL_ERROR_NO_CYPHER_OVERLAP, alert handshake failure, unable to get local issuer certificate, or tlsv1 alert protocol version. The alert number in the TLS record indicates which side rejected: alert 40 (handshake_failure), 42 (bad_certificate), 46 (certificate_unknown), 70 (protocol_version), or 112 (unrecognized_name for SNI issues).
Step 2: Test protocol version support
A frequent cause is protocol mismatch: the client offers only TLS 1.2 while the server disabled everything below 1.3, or vice versa. Test each version explicitly:
openssl s_client -connect example.com:443 -servername example.com -tls1_3
openssl s_client -connect example.com:443 -servername example.com -tls1_2
openssl s_client -connect example.com:443 -servername example.com -tls1_1If -tls1_2 succeeds but -tls1_3 fails (or the reverse), you have found a protocol-version issue. Update the client library, enable the missing version on the server, or align both sides on a shared version. Note that TLS 1.0 and 1.1 are deprecated and disabled by default in modern browsers and OpenSSL 3.x.
Step 3: Check cipher suite overlap
Even with matching protocol versions, the two sides must share at least one cipher suite. List what the server offers:
nmap --script ssl-enum-ciphers -p 443 example.comCompare against your client's supported list (openssl ciphers -v). If the server only exposes AEAD ciphers (AES-GCM, ChaCha20-Poly1305) and your client is an older embedded device that only speaks CBC-mode suites, the handshake will fail with alert 40. Force a specific cipher to test:
openssl s_client -connect example.com:443 -cipher 'ECDHE-RSA-AES128-GCM-SHA256'Step 4: Validate the certificate chain
Expired, self-signed, or hostname-mismatched certificates cause the client to abort. Inspect the presented chain:
openssl s_client -connect example.com:443 -servername example.com -showcertsVerify the notAfter date, the CN/SAN entries, and that intermediate certificates are included. A very common misconfiguration is a server sending only its leaf certificate without the intermediate. Browsers on desktop often cache intermediates and succeed, but mobile clients, curl, and Java apps fail. Fix by bundling the full chain in your server configuration.
Step 5: Verify SNI is being sent correctly
Server Name Indication (SNI) tells the server which virtual host you want. Without it, the server may present the default cert or refuse the connection with alert 112. Always include -servername in openssl tests and confirm curl is sending the correct Host header. If a load balancer or CDN sits in front, ensure the SNI matches the certificate installed there, not the origin.
Step 6: Rule out client certificates, OCSP, and clock drift
Three subtler causes remain. First, if the server requires mutual TLS, it sends a CertificateRequest and the client must respond with a certificate signed by an accepted CA. Check the openssl output for Acceptable client certificate CA names. Second, OCSP stapling failures can trigger revocation errors. Test with:
openssl s_client -connect example.com:443 -statusLook for OCSP Response Status: successful. Third, if the client's system clock drifts more than a few minutes, valid certificates appear expired or not-yet-valid. Sync with NTP: timedatectl status on Linux, w32tm /query /status on Windows.
Step 7: Packet-level analysis with Wireshark
When openssl and curl leave you without answers, capture packets. Filter with tcp.port == 443 and inspect the Client Hello and Server Hello records. You can see the exact cipher list offered, extensions sent (including SNI), the server's selected version, and any Alert record with a specific reason. This is the most authoritative source of truth and often reveals middleboxes stripping extensions or downgrading the connection.
Quick reference table
| Symptom | Likely cause | Fix |
|---|---|---|
| alert 70 protocol_version | TLS version mismatch | Enable common version on both sides |
| alert 40 handshake_failure | No shared cipher | Update cipher suites |
| unable to get local issuer | Missing intermediate | Bundle full chain |
| alert 112 unrecognized_name | SNI mismatch | Send correct servername |
| certificate has expired | Real expiry or clock drift | Renew cert or sync NTP |
Work through these steps sequentially and you will resolve nearly every handshake failure you encounter.