TLS Handshake Errors: How to Diagnose and Fix Them (TLS 1.3)

The definitive troubleshooting guide for TLS handshake failures and migration to TLS 1.3. Learn how to diagnose Cipher Mismatch, SNI, and chain errors with OpenSSL and cURL.


TL;DR TLS handshake errors occur before any application data is exchanged, which is why they leave no traces in app logs. The four root causes account for over 90% of failures: cipher suite mismatch, incomplete certificate chain, missing SNI, and protocol version incompatibility. Each can be isolated in under five minutes using openssl s_client. In TLS 1.3 environments specifically, legacy clients (unpatched Java 8, .NET Framework <4.7) are the most common failure source because they cannot negotiate the reduced cipher suite TLS 1.3 mandates.


If you’ve ever seen SSL_ERROR_NO_CYPHER_OVERLAP, Could not create SSL/TLS secure channel, or watched a connection drop silently right after the initial packet, you know transport-layer debugging feels like diagnosing a problem that refuses to leave evidence.

TLS handshake errors are silent by design: the failure happens before any data is exchanged, so the application server never records the attempt. The only place the error surfaces is the client.

This guide walks through the four failure categories, the exact commands to reproduce and isolate each one, and safe fixes—with specific attention to the behavioral changes introduced in TLS 1.3.


What is a TLS Handshake?

A TLS handshake is the negotiation sequence where client and server agree on three things before any application data flows: the protocol version, the cipher suite to use, and the validity of the server’s identity via its certificate chain.

The handshake fails when any one of those three agreements cannot be reached.

Concretely: if the server requires TLS 1.3 but the client only supports TLS 1.2, the connection resets at the ClientHello. If the certificate chain is incomplete, the client rejects it as untrusted. If SNI is missing, the server presents the wrong certificate. In all three cases, the connection is terminated before the HTTP request begins.


TLS Handshake Error Reference Table

Use this table to match a specific error to its most likely cause and the recommended diagnostic command.

Symptom / ErrorRoot CauseRecommended Action
SSL_ERROR_NO_CYPHER_OVERLAPCipher mismatch — no algorithms in commonAudit cipher suites on server; verify client library supports TLS 1.3 ciphers
Untrusted Root / Verify FailedIncomplete certificate chain — missing intermediatesRebuild chain: cat cert.crt intermediate.crt > fullchain.crt
Connection reset after ClientHelloProtocol version incompatibility or MITM proxyForce version test with OpenSSL; isolate via mobile hotspot
Hostname MismatchSNI not sent by client, or wrong certificate servedVerify client sends -servername flag; test with and without SNI
Generic Handshake FailureMinimum TLS version requirement not metConfirm client supports TLS 1.2 or TLS 1.3

The Most Common TLS 1.3 Error: Cipher Mismatch

SSL_ERROR_NO_CYPHER_OVERLAP is the single most frequent handshake error when migrating to TLS 1.3.

TLS 1.3 reduced the cipher suite from 37 options (in TLS 1.2) to 5 mandatory algorithms, removing all RC4, 3DES, DES, MD5, and SHA-1-based ciphers. Legacy clients that only knew those removed ciphers have no valid option to propose, so the handshake fails immediately.

Affected environments by default:

  • Java 8 prior to update 261
  • .NET Framework prior to version 4.7
  • OpenSSL prior to version 1.1.1
  • Android 4.x and earlier

Why it works in Chrome but fails in curl or Python: Modern browsers implement AIA Chasing — they automatically fetch missing intermediate certificates from URLs embedded in the certificate itself. HTTP clients and code libraries do not. They require the complete chain to be present on the server at connection time.


How to Diagnose TLS Handshake Errors with OpenSSL

openssl s_client is the definitive tool for TLS handshake debugging. It performs a full connection attempt and outputs the exact negotiation outcome — including which protocol version was agreed upon, which cipher was selected, and the full certificate chain received.

Step 1: Full Connection and Chain Inspection

Terminal window
openssl s_client -connect your-domain.com:443 -servername your-domain.com -showcerts

What to look for: In the output, find the SSL-Session block. The Protocol field shows the negotiated version. If it reads TLSv1.3, basic negotiation succeeded. If the command returns verify error:num=20 or unable to get local issuer certificate, the chain is incomplete.

Step 2: Isolate the Protocol Version

Terminal window
# Test TLS 1.2 support
openssl s_client -tls1_2 -connect your-domain.com:443 -servername your-domain.com
# Test TLS 1.3 support
openssl s_client -tls1_3 -connect your-domain.com:443 -servername your-domain.com

Interpretation: If -tls1_2 succeeds and -tls1_3 fails, the server has TLS 1.3 disabled or a network appliance between client and server is terminating TLS 1.3 ClientHello packets. If -tls1_3 succeeds and -tls1_2 fails, the server enforces a minimum version of TLS 1.3.

Step 3: Test SNI Behavior

Terminal window
# With SNI — correct
openssl s_client -connect your-domain.com:443 -servername your-domain.com
# Without SNI — reveals the default certificate served
openssl s_client -connect your-domain.com:443

Interpretation: In environments serving multiple domains from a single IP (CDNs, reverse proxies, shared hosting), the two commands will return different certificates. If the “without SNI” result shows a certificate for a different domain, that explains any Hostname Mismatch error from clients that don’t send SNI.


SNI: Why It Matters in CDN and Multi-Tenant Environments

SNI (Server Name Indication) is a TLS extension that tells the server which domain the client is connecting to, before the certificate is presented.

Without SNI, a server hosting 500 domains behind a single IP address has no way to know which certificate to serve during the handshake. It falls back to a default — which almost certainly won’t match the domain the client requested.

When SNI failures occur:

  • Client connects via IP address directly (e.g., https://203.0.113.1) rather than hostname
  • Client library is too old to support the SNI extension (TLS 1.0 era libraries)
  • Internal tools that bypass DNS and connect via /etc/hosts entries without updating the SNI value

The fix: Always pass the hostname explicitly. In curl: curl --resolve your-domain.com:443:IP_ADDRESS https://your-domain.com. In OpenSSL: -servername your-domain.com.


Pre-Escalation Checklist

Before opening a support ticket, verify these four conditions. They account for the majority of “mysterious” handshake failures:

  1. Clock synchronization: TLS certificates have exact validity windows. A client or server with a clock skewed by more than a few minutes will cause the certificate to appear expired or not-yet-valid, failing the notBefore/notAfter check. Verify with timedatectl status (Linux) or w32tm /query /status (Windows).
  2. Corporate MITM proxies: If the error only occurs inside an office network or VPN, a proxy performing TLS inspection is the most likely cause. The proxy presents its own certificate, which the client may not trust. Isolate by testing from a mobile hotspot — if the error disappears, the network is the variable.
  3. Complete certificate chain: The server must send the full chain: leaf certificate + all intermediates. The root CA is not sent (clients already have it). Build the chain: cat your-domain.crt intermediate.crt > fullchain.crt. Verify with openssl verify -CAfile ca-bundle.crt fullchain.crt.
  4. Library version for TLS 1.3 ciphers: TLS 1.3 uses ChaCha20-Poly1305 and AES-256-GCM-SHA384 exclusively. These require OpenSSL 1.1.1 or later. Check with openssl version.

Conclusion

TLS handshake errors follow a short list of logical failure modes — version mismatch, cipher mismatch, incomplete chain, missing SNI. None of them require guesswork. openssl s_client with the right flags makes each one reproducible and isolatable in under five minutes.

For production environments serving traffic across many domains, centralizing TLS policy management at the network edge eliminates an entire class of per-server configuration errors. Certificate renewal, cipher suite enforcement, and protocol minimums can be applied globally rather than per instance.


Frequently Asked Questions

What is a TLS handshake error? A TLS handshake error is a failure during the negotiation phase of a TLS connection, before any application data is exchanged. It means the client and server could not agree on a protocol version, cipher suite, or certificate validity. Because the failure occurs pre-application, it leaves no record in application logs — only in client-side error messages.

What causes SSL_ERROR_NO_CYPHER_OVERLAP? SSL_ERROR_NO_CYPHER_OVERLAP means the client and server share no cipher suite in common. It is the most common TLS 1.3 migration error because TLS 1.3 reduced the cipher suite from 37 options to 5, removing all legacy algorithms. The fix is to update the client library (e.g., OpenSSL to 1.1.1+, Java to 8u261+, .NET to 4.7+) or configure the server to also accept TLS 1.2 as a fallback during migration.

What is SNI and why does it cause handshake failures? SNI (Server Name Indication) is a TLS extension that allows a client to specify which domain it is connecting to at the start of the handshake, before any certificate is sent. Without SNI, servers hosting multiple domains behind a single IP address serve their default certificate, which typically doesn’t match the requested domain. This produces a Hostname Mismatch error. SNI support has been standard since TLS 1.2; failures occur primarily with very old client libraries or when connecting via IP address.

How do I check if a TLS certificate chain is complete? Run openssl s_client -connect your-domain.com:443 -servername your-domain.com -showcerts and count the certificates in the output. A complete chain includes the leaf certificate (your domain) plus all intermediate certificates. The root CA is not included. If verify return:0 appears in the output, the chain is valid. If you see verify error:num=20:unable to get local issuer certificate, an intermediate is missing.

Why does TLS work in Chrome but fail in curl or a Python script? Chrome and other modern browsers implement AIA Chasing — they automatically fetch missing intermediate certificates from URLs embedded in the leaf certificate’s Authority Information Access field. curl, Python’s requests library, and most HTTP clients do not implement AIA Chasing. They require the server to present the complete chain at connection time. The fix is always on the server: build a fullchain.crt that includes all intermediates.

How do I test whether a server supports TLS 1.2 vs. TLS 1.3? Use openssl s_client -tls1_2 -connect your-domain.com:443 -servername your-domain.com for TLS 1.2 and openssl s_client -tls1_3 -connect your-domain.com:443 -servername your-domain.com for TLS 1.3. A successful connection returns Verify return code: 0 (ok). A failure returns handshake failure or a connection reset. If TLS 1.2 succeeds but TLS 1.3 fails, either the server has TLS 1.3 disabled or a network appliance is blocking TLS 1.3 ClientHello packets.

Can a clock skew cause TLS handshake failures? Yes. TLS certificates contain notBefore and notAfter fields. If the client’s system clock is more than a few minutes ahead or behind the actual time, a valid certificate can appear expired or not-yet-valid. This is especially common in containerized environments, VMs after sleep/resume, or IoT devices with no NTP sync. Verify clock status with timedatectl status on Linux or w32tm /query /status on Windows.


stay up to date

Subscribe to our Newsletter

Get the latest product updates, event highlights, and tech industry insights delivered to your inbox.