A TLS handshake failure tells you that client and server could not agree on how to talk securely, but the message most tools print, such as "handshake failure" or curl error 35, hides which of a dozen mismatches caused it. The real cause is usually one of a few things: no common protocol version, no common cipher suite or signature algorithm, a missing or wrong SNI name, a client certificate the server required and did not get, an ALPN mismatch, or something in the path that is not speaking TLS at all. This guide maps the alerts and error strings to those causes, shows how to isolate each one with openssl s_client, packet captures and server logs, and gives the fix for each without lowering the security baseline. It is for platform, network and application engineers and for security teams who get the ticket after a hardening change. Error strings were checked against the OpenSSL, Go, OpenJDK, curl and Chromium sources on 11 November 2026.
Key takeaways
- Find out who sent the alert. "Received" alert means the other side rejected you; a locally generated failure means your own stack refused. The fix is on opposite ends.
- The alert number narrows the cause. 40 handshake_failure, 70 protocol_version, 112 unrecognized_name, 116 certificate_required, 120 no_application_protocol and 48 unknown_ca each point to a different mismatch.
- Reproduce with
openssl s_client, one variable at a time. Force a version, a cipher list, a server name or a client certificate and see which change makes the handshake succeed. - Most failures follow a change. Disabling TLS 1.0 and 1.1, a new load balancer security policy, a certificate with a different key type or a new proxy explains most incidents. Check the change log first.
- Never fix by downgrading globally. Re-enabling retired protocols or weak suites for one legacy client exposes every client. Isolate the legacy client behind its own listener and plan its upgrade.
How the handshake breaks
In TLS 1.3 (RFC 8446) the client sends a ClientHello listing supported versions, cipher suites, key exchange groups, signature algorithms, the server name (SNI) and application protocols (ALPN). The server picks one of each, sends its certificate and proves possession of the key, and may request a client certificate. Any step can fail, and RFC 8446 says a server that cannot negotiate a supported parameter set must abort with a handshake_failure or insufficient_security alert. RFC 8446 defines handshake_failure as indicating "that the sender was unable to negotiate an acceptable set of security parameters given the options available". The How TLS Works Guide explains the handshake itself; this guide assumes you know it and focuses on the failure.
Alert and error reference
Alert numbers are from RFC 8446 and the OpenSSL and Go source. Strings are quoted from current OpenSSL (crypto/err/openssl.txt), Go (crypto/tls) and OpenJDK source.
| Alert (number) | OpenSSL reason when received | Go when received | Most likely cause |
|---|---|---|---|
| handshake_failure (40) | tls alert handshake failure | remote error: tls: handshake failure | No shared cipher suite, group or signature algorithm; client certificate missing in TLS 1.2 |
| bad_certificate (42) | tls alert bad certificate | remote error: tls: bad certificate | Peer rejected the certificate you sent (often a client certificate in mTLS) |
| certificate_expired (45) | Peer-specific | remote error: tls: expired certificate | Your certificate has expired |
| unknown_ca (48) | tlsv1 alert unknown ca | remote error: tls: unknown certificate authority | Peer does not trust the issuer of your certificate |
| protocol_version (70) | tlsv1 alert protocol version | remote error: tls: protocol version not supported | No common TLS version |
| unrecognized_name (112) | tlsv1 unrecognized name | remote error: tls: unrecognized name | Server has no certificate or virtual host for the SNI name sent |
| certificate_required (116) | tlsv13 alert certificate required | remote error: tls: certificate required | TLS 1.3 server requires a client certificate and none was sent |
| no_application_protocol (120) | tlsv1 alert no application protocol | Peer-specific | No common ALPN value, for example a gRPC client requiring h2 |
Locally generated failures read differently. On a Go server, tls: no cipher suite supported by both client and server, tls: client offered only unsupported versions and tls: client didn't provide a certificate name the cause directly. OpenSSL reports no shared cipher, unsupported protocol, no protocols available, no suitable signature algorithm or peer did not return a certificate. Java prints Received fatal alert: followed by the alert name when the peer sent it, and No appropriate protocol (protocol is disabled or cipher suites are inappropriate) when its own configuration leaves nothing to offer. curl returns exit code 35 (CURLE_SSL_CONNECT_ERROR), whose documentation advises reading the error buffer because it "pinpoints the problem slightly more".
Likely causes, most to least common
- Protocol version mismatch: a server or load balancer policy now requires TLS 1.2 or 1.3, and an old client, library or embedded device only offers TLS 1.0 or 1.1, or the reverse with a client pinned to TLS 1.3 against an old server. Chromium reports
ERR_SSL_VERSION_OR_CIPHER_MISMATCH, which its source describes as client and server not supporting a common protocol version or cipher suite. - Cipher suite or signature algorithm mismatch: the client offers only suites the server has removed (CBC or RSA key exchange suites), or the certificate key type does not fit what the client accepts, for example an ECDSA certificate presented to a client that offers only RSA suites in TLS 1.2.
- Client certificate required but not sent: mTLS endpoints reject callers that send no certificate, an expired one or one from an untrusted issuer. In TLS 1.3 this is alert 116; in TLS 1.2 it is usually 40. Chromium shows
ERR_BAD_SSL_CLIENT_AUTH_CERT. - SNI problems: the client connects by IP address or an internal alias and sends no SNI or the wrong name, so the server returns a default certificate or the
unrecognized_namealert. Old clients that do not send SNI fail on shared listeners. - Something in the path is not TLS: connecting with TLS to a plain HTTP port, or through a proxy that answers in plain text, produces OpenSSL's
wrong version numberor Go'sfirst record does not look like a TLS handshake. A connection cut by a firewall or middlebox shows asunexpected eof while reading. - ALPN mismatch: HTTP/2 or gRPC clients that require
h2fail against a server or proxy offering onlyhttp/1.1. - TLS inspection: a proxy re-terminates TLS with its own policy and may not support the version, group or client certificate flow the destination needs. See the TLS Inspection Guide.
How to diagnose it
- Record the change history: list TLS policy changes, certificate replacements, load balancer or proxy updates and client library upgrades in the last few days on both sides.
- Get both sides' view: collect the client error and the server or load balancer log for the same timestamp. On Windows, Schannel errors go to the System event log, and the
EventLoggingDWORD underHKLM\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNELsets the level (7 logs errors, warnings, informational and success events; Microsoft notes a reboot is required after changing it). - Baseline with
openssl s_client:openssl s_client -connect host:443 -servername host -brief.-briefprints the negotiated version, cipher and peer certificate summary when it works. - Vary one parameter at a time: force versions with
-tls1_2or-tls1_3; restrict TLS 1.2 ciphers with-cipherand TLS 1.3 suites with-ciphersuites; set-sigalgsor-groups; test SNI with a different-servernameor with-noservername; offer ALPN with-alpn h2,http/1.1; present a client certificate with-certand-key. The variant that succeeds identifies the mismatch. - Test from the client's network position: repeat from the failing host or pod. A proxy, NAT gateway or service mesh sidecar can mean the client is not talking to the server you tested.
- Capture the ClientHello: a packet capture filtered on the TLS handshake shows exactly which versions, suites, groups, SNI and ALPN the client offered and which alert came back, and from which side. For Java clients,
-Djavax.net.debug=ssl:handshakeprints each handshake message. - For protocol-switching services: mail, LDAP and database protocols often start in plain text and upgrade; test with
openssl s_client -starttls smtp(orldap,postgres,mysqland others) rather than direct TLS.
The OpenSSL for Security Teams guide covers these commands in more detail.
Fixes by cause
| Cause | Correct fix | Avoid |
|---|---|---|
| Client only offers TLS 1.0 or 1.1 | Upgrade the client library or runtime; if impossible, isolate it behind a dedicated listener with its own policy and an end date | Re-enabling old versions on the shared listener |
| No shared cipher suite | Add a modern AEAD suite to the client, or align the server to a published baseline that the client supports | Adding RSA key exchange or CBC suites for everyone |
| Certificate key type unsupported by client | Serve both an ECDSA and an RSA certificate where the server supports dual certificates, or choose the type the client population needs | Weakening signature algorithms |
| Client certificate missing or rejected | Check the client is configured to send its certificate and chain, that it is valid and that its issuer is in the server's client CA list | Turning client authentication off |
| SNI missing or wrong | Connect by the certificate's DNS name, set the server name in the client, or add the name to the server configuration | Making a wildcard default certificate catch everything |
| Plain text on a TLS port | Fix the port, scheme or proxy configuration | Disabling TLS on the client |
| ALPN mismatch | Enable h2 end to end on proxies and load balancers in the path | Forcing HTTP/1.1 on gRPC clients |
| Inspection proxy cannot handle the destination | Add a documented bypass for that destination | Disabling certificate verification on the client |
When a server certificate is not trusted, the failure is usually reported as a verification error rather than a handshake alert; the x509 Certificate Signed by Unknown Authority Troubleshooting Guide covers that case, and the Expired Certificate Outage Troubleshooting Guide covers expiry.
Testing and rollback for TLS policy changes
- Measure before you tighten: log the negotiated version and cipher per connection for at least a full business cycle, and list clients still using what you plan to remove.
- Stage the change: apply the new policy to a test listener or a canary node first, and run each known client type against it.
- Roll out in rings: internal endpoints, then partner endpoints with notice, then public endpoints.
- Keep the old policy definition ready: rollback should be a one-step policy reassignment on the listener, not a rebuild.
- Track exceptions: every legacy listener created during rollout gets an owner and a removal date.
The target baseline is set out in the TLS Server Configuration Baseline Guide. Downgrade risks, including why fallback to older versions is dangerous, are covered in the Historic TLS Attacks Guide.
Decision guidance
- Legacy device you cannot upgrade: terminate its TLS on a separate, tightly scoped listener or gateway, restrict who can reach it and record the risk. Do not let one device set the policy for everyone.
- Partner integration failing after your hardening: share the exact policy (versions, suites, groups, certificate key type) and a test endpoint; most partners can fix a client configuration quickly when they know what to change.
- mTLS service-to-service failures: treat them as identity problems first: which client certificate was sent, who issued it, and whether the server trusts that issuer.
- Small organisation: use the defaults of a maintained server or managed load balancer policy rather than hand-written cipher lists; most handshake failures in small estates come from copied old configurations.
- Large organisation: publish named TLS profiles centrally and log the negotiated parameters everywhere, so the next policy change can be checked against real traffic.
Where identity fits
The handshake is where a workload proves its identity. In mTLS, failures with alerts 42, 48 and 116 are almost always about a client identity: a certificate not sent, expired, or issued by a CA the server does not trust. Agents and service accounts that call APIs over mTLS inherit the same failure modes, and the temptation to "just disable client auth" removes the workload's identity check. See the mTLS Guide and the Machine Identity, PKI and Certificate Lifecycle Guide.
Detection and measurement
- Handshake failure rate: per listener, from load balancer, proxy or web server logs; alert on a step change after any deployment.
- Negotiated parameters: share of connections by TLS version and cipher, so you know who breaks before you tighten.
- Alert breakdown: counts by alert type (40, 42, 48, 70, 112, 116, 120) point to the cause class without packet captures.
- Downgrade attempts: unexpected connections offering retired versions can indicate old software or a downgrade attempt (MITRE ATT&CK T1689 Downgrade Attack).
- Metrics: handshake failure rate per listener; percentage of traffic on TLS 1.3; number of legacy exception listeners and their age; mean time to identify the cause of a handshake incident; mTLS client certificates expiring in the next 30 days.
Framework mapping
| Framework | Reference | Relevance |
|---|---|---|
| NIST SP 800-53 Rev. 5 | SC-8 Transmission Confidentiality and Integrity; SC-13 Cryptographic Protection; SC-23 Session Authenticity; IA-5(2) Public Key-based Authentication; CM-6 Configuration Settings | Protocol and cipher policy, mTLS client authentication, managed configuration |
| NIST CSF 2.0 | PR.DS-02; PR.AA-03; DE.CM-01 | Data in transit, service authentication, network monitoring |
| CIS Controls v8.1 | 3.10 Encrypt Sensitive Data in Transit | Requires working, modern TLS |
| ISO/IEC 27001:2022 Annex A | A.8.24 Use of cryptography; A.8.20 Networks security; A.8.32 Change management | Cryptographic policy, network protection, controlled policy changes |
| MITRE ATT&CK | T1689 Downgrade Attack; T1557 Adversary-in-the-Middle | Why global downgrades and verification bypasses are unsafe fixes |
| MITRE D3FEND | D3-CAA Connection Attempt Analysis | Analysing failed connection attempts as a detection signal |
Common failure patterns
- Testing from the wrong place: the handshake works from an engineer's laptop and fails from the pod, because the pod goes through a proxy or sidecar with a different policy.
- Testing by IP address:
s_clientwithout the right-servernamereceives a default certificate or anunrecognized_namealert and sends the investigation in the wrong direction. - Global downgrade: TLS 1.0 re-enabled on the main listener for one client, then never removed.
- Certificate swap breaks old clients: moving from RSA to ECDSA without checking that every client supports it.
- Client auth disabled to restore service: an mTLS failure "fixed" by turning off client certificate checks, leaving the API open to any caller who can reach it.
Practitioner checklist
- Establish which side sent the alert and record its name and number.
- Check recent TLS policy, certificate, proxy and client library changes on both sides.
- Reproduce with
openssl s_client -servernamefrom the failing client's network position. - Vary version, ciphers, groups, SNI, ALPN and client certificate one at a time until the handshake succeeds.
- Capture the ClientHello when logs are not conclusive.
- Fix the client or the specific listener; never downgrade the shared baseline.
- Give every legacy exception an owner and an end date.
- Log negotiated version and cipher per connection before the next hardening change.
Standards and references
- IETF: RFC 8446 The Transport Layer Security (TLS) Protocol Version 1.3
- OpenSSL: openssl-s_client
- curl: libcurl error codes
- Go: crypto/tls alert definitions
- OpenSSL: error reason strings (crypto/err/openssl.txt)
- Microsoft Learn: TLS registry settings
- MITRE ATT&CK: Downgrade Attack
Related NHI Mgmt Group resources: How TLS Works Guide · TLS Server Configuration Baseline Guide · mTLS Guide · x509 Certificate Signed by Unknown Authority Troubleshooting Guide