# SSL handshake failed: find the cause by client and server

> SSL handshake failed is a generic message. Map it to the exact error your browser, app, CDN or server reports, find the failing step and fix it.

Source: https://ssl.sh/errors/ssl-handshake-failed/
Last updated: 2026-11-10

**"SSL handshake failed" means the client and the server could not finish setting up a TLS connection, so nothing was encrypted and no request was sent.** It is a generic message: Cloudflare shows it as Error 525, and browsers and apps use more specific codes. Find who reported it and at which step, then use the tables below.

## Where a handshake can fail

A TLS handshake runs through a few steps; each has typical failures. The [TLS handshake walk-through](https://ssl.sh/learn/tls-handshake/) explains the steps in detail.

| Step | What happens | Typical failure |
|---|---|---|
| 1. Connect | TCP connection to port 443 | Port closed, firewall, or the port speaks plain HTTP |
| 2. ClientHello | Client offers versions, cipher suites, SNI hostname | No common version or cipher; no certificate for that name |
| 3. Server certificate | Server sends its certificate chain | Expired, untrusted, wrong name, missing intermediate |
| 4. Client certificate (optional) | Server asks the client to authenticate | No or rejected client certificate |
| 5. Finished | Keys confirmed, encryption starts | Interference by proxies or antivirus, resets |

The side that notices the problem usually sends an alert. RFC 9846 defines, among others, `handshake_failure` (40), `protocol_version` (70), `unrecognized_name` (112) and `certificate_required` (116).

## Client-side messages

### Browsers

| Message | Step | Page |
|---|---|---|
| `ERR_SSL_PROTOCOL_ERROR` | 1–2 | [ERR_SSL_PROTOCOL_ERROR](https://ssl.sh/errors/err-ssl-protocol-error/) |
| `ERR_SSL_VERSION_OR_CIPHER_MISMATCH` | 2 | [version or cipher mismatch](https://ssl.sh/errors/err-ssl-version-or-cipher-mismatch/) |
| `ERR_SSL_UNRECOGNIZED_NAME_ALERT` | 2 | [unrecognized name alert](https://ssl.sh/errors/err-ssl-unrecognized-name-alert/) |
| `SSL_ERROR_HANDSHAKE_FAILURE_ALERT` | 2 or 4 | [Firefox handshake failure alert](https://ssl.sh/errors/ssl-error-handshake-failure-alert/) |
| `SSL_ERROR_NO_CYPHER_OVERLAP` | 2 | [no cypher overlap](https://ssl.sh/errors/ssl-error-no-cypher-overlap/) |
| `SSL_ERROR_RX_RECORD_TOO_LONG` | 1 | [record too long](https://ssl.sh/errors/ssl-error-rx-record-too-long/) |
| `PR_END_OF_FILE_ERROR` | 1–5 | [PR_END_OF_FILE_ERROR](https://ssl.sh/errors/pr-end-of-file-error/) |
| `ERR_BAD_SSL_CLIENT_AUTH_CERT` | 4 | [client certificate rejected](https://ssl.sh/errors/err-bad-ssl-client-auth-cert/) |
| `NET::ERR_CERT_AUTHORITY_INVALID`, `DLG_FLAGS_INVALID_CA` | 3 | [untrusted issuer](https://ssl.sh/errors/net-err-cert-authority-invalid/), [IE mode](https://ssl.sh/errors/dlg-flags-invalid-ca/) |
| "Safari can't establish a secure connection to the server" | 1–2 | [Safari](https://ssl.sh/errors/safari-cant-establish-secure-connection/) |

Certificate errors at step 3 in browsers usually show an interstitial such as [Your connection is not private](https://ssl.sh/errors/your-connection-is-not-private/) rather than a plain handshake error.

### Apps, languages and command-line tools

| Message | Page |
|---|---|
| `NSURLErrorDomain Code=-1200` | [iOS secure connection failed](https://ssl.sh/errors/nsurlerrordomain-1200/) |
| `NSURLErrorDomain Code=-1202` | [iOS untrusted certificate](https://ssl.sh/errors/ios-nsurlerrordomain-1202/) |
| Flutter `HandshakeException ... CERTIFICATE_VERIFY_FAILED` | [Flutter](https://ssl.sh/errors/flutter-handshake-exception-certificate-verify-failed/) |
| Android `Trust anchor for certification path not found` | [Android trust anchor](https://ssl.sh/errors/android-trust-anchor-not-found/) |
| `javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure` | [Java handshake_failure](https://ssl.sh/errors/java-sslhandshakeexception-handshake-failure/) |
| Python `SSLEOFError` / `WRONG_VERSION_NUMBER` | [EOF in violation of protocol](https://ssl.sh/errors/python-ssl-eof-occurred-in-violation-of-protocol/), [wrong version number](https://ssl.sh/errors/python-ssl-wrong-version-number/) |
| `curl: (35)` | [curl 35](https://ssl.sh/errors/curl-35-ssl-connect-error/) |
| `curl: (60)` | [curl 60](https://ssl.sh/errors/curl-60-ssl-certificate-problem/) |

In OpenSSL-based tools a `handshake_failure` alert from the server appears as `sslv3 alert handshake failure` (OpenSSL 1.1.1 and 3.0); newer OpenSSL versions word it as `ssl/tls alert handshake failure`.

## Server, proxy and CDN messages

| Where | Message | Page |
|---|---|---|
| Cloudflare | Error 525: SSL handshake failed | [Cloudflare 525](https://ssl.sh/errors/cloudflare-error-525-ssl-handshake-failed/) |
| Cloudflare | Error 526: invalid SSL certificate | [Cloudflare 526](https://ssl.sh/errors/cloudflare-error-526-invalid-ssl-certificate/) |
| nginx | `SSL_do_handshake() failed`, `no shared cipher`, `peer closed connection in SSL handshake` | Diagnose from outside, below |
| Apache | AH02572, AH01909 at startup | [AH02572](https://ssl.sh/errors/apache-ah02572-failed-to-configure-certificate/), [AH01909](https://ssl.sh/errors/apache-ah01909-certificate-does-not-include-id/) |
| IIS | A specified logon session does not exist | [IIS binding](https://ssl.sh/errors/iis-specified-logon-session-does-not-exist/) |
| Windows | Schannel events | [Schannel errors](https://ssl.sh/errors/windows-schannel-errors/) |

nginx logs many handshake failures caused by clients, including `no shared cipher`, unsupported protocol versions and received alerts, at **info** level. With the default `error_log ... error;` you never see them. Raise the level temporarily:

```nginx
error_log /var/log/nginx/error.log info;
```

## Diagnose from outside

Run these from a machine outside the failing network:

```bash
openssl s_client -connect example.com:443 -servername example.com -brief </dev/null
openssl s_client -connect example.com:443 -servername example.com -tls1_2 -brief </dev/null
openssl s_client -connect example.com:443 -servername example.com -tls1_3 -brief </dev/null
```

- `wrong version number` or `packet length too long`: the port is not speaking TLS (step 1).
- An alert such as `handshake failure` or `protocol version`: version, cipher or client certificate problem (steps 2 and 4).
- The handshake completes but `Verify return code` is not 0: certificate problem (step 3).
- Works without `-servername` but not with it, or the reverse: SNI configuration.

The [SSL checker](https://ssl.sh/tools/ssl-checker/) runs the same tests and shows protocol support and the chain. To fix protocol and cipher settings, start from the [TLS config generator](https://ssl.sh/tools/tls-config-generator/); for chain problems see [missing intermediate certificate](https://ssl.sh/errors/missing-intermediate-certificate/).

## How to verify the fix

1. All three `openssl s_client` commands above complete, or fail only for versions you intentionally disabled.
2. The client that originally failed connects, on the same network.
3. With info-level logging enabled for a while, the server log shows no new handshake errors from real clients; then restore the normal log level.
