# SSL handshake failed（SSL 握手失败）：按客户端和服务端排查

> SSL handshake failed 是个笼统的提示。先找到浏览器、App、CDN 或服务器给出的具体报错，判断握手卡在哪一步，再按对应页面修复。

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

**“SSL handshake failed” 的意思是：客户端和服务器没能把 TLS 连接建立起来，没有任何内容被加密，请求也没有发出去。** 这是一个笼统的提示：Cloudflare 把它显示为 Error 525，各种库和日志里也常打印这句话，每个浏览器和 App 还有自己更具体的错误代码。修之前先弄清是谁报的错、握手卡在哪一步。

## 先分清你是访客还是站长

访客先确认是不是网站的问题，站长先找到具体报错和同一时间的服务器日志。

**普通访客：** 先用 [SSL 检测](https://ssl.sh/zh/tools/ssl-checker/) 从外部测一下网站。外部也失败，说明是网站的问题，只能等站长修；外部正常，就依次试换手机流量、换浏览器、关闭杀毒软件的 HTTPS 扫描，哪一步之后好了，原因就在哪。

**站长：** 先看用户报的是哪种具体错误，再看服务器错误日志在同一时间有没有记录。只有部分用户失败，多半和他们的设备、网络有关；所有人都失败，基本是证书、端口或协议配置的问题。最近改过配置、续过证书、换过 CDN 的，优先从这些改动查起。

**App 开发者：** 把完整的异常信息记下来，尤其是冒号后面的具体原因和底层错误码。同一个接口，浏览器正常而 App 失败，最常见的原因是服务器缺中间证书，或者 App 运行环境的信任库和电脑不一样。

## 握手可能在哪一步失败

TLS 握手分几个步骤，每一步都有典型的失败方式，详细过程见 [TLS 握手过程详解](https://ssl.sh/zh/learn/tls-handshake/)。

| 步骤 | 发生了什么 | 典型失败 |
|---|---|---|
| 1. 建立连接 | 和 443 端口建立 TCP 连接 | 端口没开、防火墙拦截，或者端口上跑的是明文 HTTP |
| 2. ClientHello | 客户端报出支持的版本、加密套件和 SNI 域名 | 没有共同的版本或套件；没有这个域名的证书 |
| 3. 服务器证书 | 服务器发送证书链 | 过期、不受信任、域名不对、缺中间证书 |
| 4. 客户端证书（可选） | 服务器要求客户端出示证书 | 没有客户端证书，或者证书被拒绝 |
| 5. Finished | 确认密钥，开始加密通信 | 代理或杀毒软件干扰、连接被重置 |

发现问题的一方通常会发送一条警报。RFC 9846 定义的警报包括 `handshake_failure`（40）、`protocol_version`（70）、`unrecognized_name`（112）、`certificate_required`（116）等。

## 客户端这边的报错

客户端报的具体代码能直接指出握手卡在哪一步，按下表找到对应页面。

### 浏览器

| 报错 | 对应步骤 | 详细页面 |
|---|---|---|
| `ERR_SSL_PROTOCOL_ERROR`（此网站无法提供安全连接） | 1–2 | [ERR_SSL_PROTOCOL_ERROR](https://ssl.sh/zh/errors/err-ssl-protocol-error/) |
| `ERR_SSL_VERSION_OR_CIPHER_MISMATCH` | 2 | [协议或套件不匹配](https://ssl.sh/zh/errors/err-ssl-version-or-cipher-mismatch/) |
| `ERR_SSL_UNRECOGNIZED_NAME_ALERT` | 2 | [unrecognized name 警报](https://ssl.sh/zh/errors/err-ssl-unrecognized-name-alert/) |
| `SSL_ERROR_HANDSHAKE_FAILURE_ALERT` | 2 或 4 | [Firefox 握手失败警报](https://ssl.sh/zh/errors/ssl-error-handshake-failure-alert/) |
| `SSL_ERROR_NO_CYPHER_OVERLAP` | 2 | [没有共同加密算法](https://ssl.sh/zh/errors/ssl-error-no-cypher-overlap/) |
| `SSL_ERROR_RX_RECORD_TOO_LONG` | 1 | [record too long](https://ssl.sh/zh/errors/ssl-error-rx-record-too-long/) |
| `PR_END_OF_FILE_ERROR` | 1–5 | [PR_END_OF_FILE_ERROR](https://ssl.sh/zh/errors/pr-end-of-file-error/) |
| `ERR_BAD_SSL_CLIENT_AUTH_CERT` | 4 | [客户端证书被拒绝](https://ssl.sh/zh/errors/err-bad-ssl-client-auth-cert/) |
| `NET::ERR_CERT_AUTHORITY_INVALID`、`DLG_FLAGS_INVALID_CA` | 3 | [签发机构不受信任](https://ssl.sh/zh/errors/net-err-cert-authority-invalid/)、[IE 模式](https://ssl.sh/zh/errors/dlg-flags-invalid-ca/) |
| Safari 无法建立安全连接 | 1–2 | [Safari](https://ssl.sh/zh/errors/safari-cant-establish-secure-connection/) |

第 3 步的证书问题，浏览器一般显示的是 [您的连接不是私密连接](https://ssl.sh/zh/errors/your-connection-is-not-private/) 这类警告页，而不是单纯的握手错误。

国内的 360、QQ 等双核浏览器：极速模式用 Chromium 内核，报错代码和 Chrome 相同；兼容模式用 IE 内核，证书问题显示为 IE 风格的页面，代码是 `DLG_FLAGS_*` 这一类。

### App、编程语言和命令行工具

| 报错 | 详细页面 |
|---|---|
| `NSURLErrorDomain Code=-1200` | [iOS 安全连接失败](https://ssl.sh/zh/errors/nsurlerrordomain-1200/) |
| `NSURLErrorDomain Code=-1202` | [iOS 证书不受信任](https://ssl.sh/zh/errors/ios-nsurlerrordomain-1202/) |
| Flutter `HandshakeException ... CERTIFICATE_VERIFY_FAILED` | [Flutter](https://ssl.sh/zh/errors/flutter-handshake-exception-certificate-verify-failed/) |
| 安卓 `Trust anchor for certification path not found` | [安卓信任锚](https://ssl.sh/zh/errors/android-trust-anchor-not-found/) |
| `javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure` | [Java handshake_failure](https://ssl.sh/zh/errors/java-sslhandshakeexception-handshake-failure/) |
| Python `SSLEOFError` / `WRONG_VERSION_NUMBER` | [EOF occurred in violation of protocol](https://ssl.sh/zh/errors/python-ssl-eof-occurred-in-violation-of-protocol/)、[wrong version number](https://ssl.sh/zh/errors/python-ssl-wrong-version-number/) |
| `curl: (35)` | [curl 35](https://ssl.sh/zh/errors/curl-35-ssl-connect-error/) |
| `curl: (60)` | [curl 60](https://ssl.sh/zh/errors/curl-60-ssl-certificate-problem/) |

基于 OpenSSL 的工具里，服务器发来的 `handshake_failure` 警报在 OpenSSL 1.1.1 和 3.0 中显示为 `sslv3 alert handshake failure`，更新的版本写成 `ssl/tls alert handshake failure`。

## 服务端、代理和 CDN 这边的报错

服务端的报错写在 Web 服务器、代理和 CDN 的日志或错误页里，同样能对应到具体原因。

| 位置 | 报错 | 详细页面 |
|---|---|---|
| Cloudflare | Error 525: SSL handshake failed | [Cloudflare 525](https://ssl.sh/zh/errors/cloudflare-error-525-ssl-handshake-failed/) |
| Cloudflare | Error 526: invalid SSL certificate | [Cloudflare 526](https://ssl.sh/zh/errors/cloudflare-error-526-invalid-ssl-certificate/) |
| nginx | `SSL_do_handshake() failed`、`no shared cipher`、`peer closed connection in SSL handshake` | 见下文 |
| Apache | 启动时的 AH02572、AH01909 | [AH02572](https://ssl.sh/zh/errors/apache-ah02572-failed-to-configure-certificate/)、[AH01909](https://ssl.sh/zh/errors/apache-ah01909-certificate-does-not-include-id/) |
| IIS | A specified logon session does not exist | [IIS 绑定证书](https://ssl.sh/zh/errors/iis-specified-logon-session-does-not-exist/) |
| Windows | Schannel 事件 | [Schannel 错误](https://ssl.sh/zh/errors/windows-schannel-errors/) |

nginx 把很多由客户端引起的握手失败，包括 `no shared cipher`、不支持的协议版本和收到的警报，都记在 **info** 级别。默认的 `error_log ... error;` 下完全看不到。排查时临时调高：

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

宝塔面板的站点日志也是 nginx 的错误日志，同样需要改日志级别才能看到这些记录。用了阿里云、腾讯云 CDN 的网站，CDN 用 HTTPS 回源时和源站之间也要握手，源站证书或协议配置有问题，表现为 CDN 节点报错而不是浏览器报错，排查思路和 Cloudflare 525 一样。

## 从外部诊断

从外部跑三条 openssl 命令，就能判断失败出在端口、协议、证书还是 SNI。在出问题的网络之外的机器上执行：

```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` 或 `packet length too long`：端口上跑的不是 TLS（第 1 步）。
- 收到 `handshake failure`、`protocol version` 之类的警报：版本、套件或客户端证书问题（第 2、4 步）。
- 握手完成但 `Verify return code` 不是 0：证书问题（第 3 步）。
- 不带 `-servername` 能成功、带上反而失败，或者反过来：SNI 配置问题。

[SSL 检测](https://ssl.sh/zh/tools/ssl-checker/) 会做同样的测试，并显示支持的协议和证书链。协议和套件配置从 [TLS 配置生成器](https://ssl.sh/zh/tools/tls-config-generator/) 开始改；证书链问题见 [缺少中间证书](https://ssl.sh/zh/errors/missing-intermediate-certificate/)。

## 怎么确认已经修好

三条 openssl 命令都完成握手、原来报错的客户端恢复正常，就算修好。

1. 上面三条 `openssl s_client` 命令都能完成握手，或者只在你有意关闭的版本上失败。
2. 原来报错的客户端在原来的网络里能正常连接。
3. info 级别日志开一段时间，服务器日志里不再出现来自真实客户端的握手错误，然后把日志级别恢复原样。
