服务器和代理
SSL handshake failed(SSL 握手失败):按客户端和服务端排查
SSL handshake failed“SSL handshake failed” 的意思是:客户端和服务器没能把 TLS 连接建立起来,没有任何内容被加密,请求也没有发出去。 这是一个笼统的提示:Cloudflare 把它显示为 Error 525,各种库和日志里也常打印这句话,每个浏览器和 App 还有自己更具体的错误代码。修之前先弄清是谁报的错、握手卡在哪一步。
先分清你是访客还是站长
访客先确认是不是网站的问题,站长先找到具体报错和同一时间的服务器日志。
普通访客: 先用 SSL 检测 从外部测一下网站。外部也失败,说明是网站的问题,只能等站长修;外部正常,就依次试换手机流量、换浏览器、关闭杀毒软件的 HTTPS 扫描,哪一步之后好了,原因就在哪。
站长: 先看用户报的是哪种具体错误,再看服务器错误日志在同一时间有没有记录。只有部分用户失败,多半和他们的设备、网络有关;所有人都失败,基本是证书、端口或协议配置的问题。最近改过配置、续过证书、换过 CDN 的,优先从这些改动查起。
App 开发者: 把完整的异常信息记下来,尤其是冒号后面的具体原因和底层错误码。同一个接口,浏览器正常而 App 失败,最常见的原因是服务器缺中间证书,或者 App 运行环境的信任库和电脑不一样。
握手可能在哪一步失败
TLS 握手分几个步骤,每一步都有典型的失败方式,详细过程见 TLS 握手过程详解。
| 步骤 | 发生了什么 | 典型失败 |
|---|---|---|
| 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 |
ERR_SSL_VERSION_OR_CIPHER_MISMATCH |
2 | 协议或套件不匹配 |
ERR_SSL_UNRECOGNIZED_NAME_ALERT |
2 | unrecognized name 警报 |
SSL_ERROR_HANDSHAKE_FAILURE_ALERT |
2 或 4 | Firefox 握手失败警报 |
SSL_ERROR_NO_CYPHER_OVERLAP |
2 | 没有共同加密算法 |
SSL_ERROR_RX_RECORD_TOO_LONG |
1 | record too long |
PR_END_OF_FILE_ERROR |
1–5 | PR_END_OF_FILE_ERROR |
ERR_BAD_SSL_CLIENT_AUTH_CERT |
4 | 客户端证书被拒绝 |
NET::ERR_CERT_AUTHORITY_INVALID、DLG_FLAGS_INVALID_CA |
3 | 签发机构不受信任、IE 模式 |
| Safari 无法建立安全连接 | 1–2 | Safari |
第 3 步的证书问题,浏览器一般显示的是 您的连接不是私密连接 这类警告页,而不是单纯的握手错误。
国内的 360、QQ 等双核浏览器:极速模式用 Chromium 内核,报错代码和 Chrome 相同;兼容模式用 IE 内核,证书问题显示为 IE 风格的页面,代码是 DLG_FLAGS_* 这一类。
App、编程语言和命令行工具
| 报错 | 详细页面 |
|---|---|
NSURLErrorDomain Code=-1200 |
iOS 安全连接失败 |
NSURLErrorDomain Code=-1202 |
iOS 证书不受信任 |
Flutter HandshakeException ... CERTIFICATE_VERIFY_FAILED |
Flutter |
安卓 Trust anchor for certification path not found |
安卓信任锚 |
javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure |
Java handshake_failure |
Python SSLEOFError / WRONG_VERSION_NUMBER |
EOF occurred in violation of protocol、wrong version number |
curl: (35) |
curl 35 |
curl: (60) |
curl 60 |
基于 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 |
| Cloudflare | Error 526: invalid SSL certificate | Cloudflare 526 |
| nginx | SSL_do_handshake() failed、no shared cipher、peer closed connection in SSL handshake |
见下文 |
| Apache | 启动时的 AH02572、AH01909 | AH02572、AH01909 |
| IIS | A specified logon session does not exist | IIS 绑定证书 |
| Windows | Schannel 事件 | Schannel 错误 |
nginx 把很多由客户端引起的握手失败,包括 no shared cipher、不支持的协议版本和收到的警报,都记在 info 级别。默认的 error_log ... error; 下完全看不到。排查时临时调高:
error_log /var/log/nginx/error.log info;
宝塔面板的站点日志也是 nginx 的错误日志,同样需要改日志级别才能看到这些记录。用了阿里云、腾讯云 CDN 的网站,CDN 用 HTTPS 回源时和源站之间也要握手,源站证书或协议配置有问题,表现为 CDN 节点报错而不是浏览器报错,排查思路和 Cloudflare 525 一样。
从外部诊断
从外部跑三条 openssl 命令,就能判断失败出在端口、协议、证书还是 SNI。在出问题的网络之外的机器上执行:
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 检测 会做同样的测试,并显示支持的协议和证书链。协议和套件配置从 TLS 配置生成器 开始改;证书链问题见 缺少中间证书。
怎么确认已经修好
三条 openssl 命令都完成握手、原来报错的客户端恢复正常,就算修好。
- 上面三条
openssl s_client命令都能完成握手,或者只在你有意关闭的版本上失败。 - 原来报错的客户端在原来的网络里能正常连接。
- info 级别日志开一段时间,服务器日志里不再出现来自真实客户端的握手错误,然后把日志级别恢复原样。
常见问题
SSL 握手失败一定是证书的问题吗?
不一定。证书问题只是其中一类原因,很多握手失败发生在检查证书之前,比如端口上没开 TLS、双方没有共同的加密套件。看具体的错误代码就知道属于哪一类。
现在不是都用 TLS 了吗,为什么还叫 SSL?
严格来说现在的连接用的都是 TLS,通常是 1.2 或 1.3,SSL 3.0 及更早的版本早已淘汰。只是报错信息和各种库沿用了老名字,所以“SSL handshake failed”还到处可见。
只有部分用户握手失败,从哪里查起?
比较这些用户有什么不同:老设备(缺根证书、不支持 TLS 1.2)、公司网络或杀毒软件在拦截 TLS、客户端不发送 SNI。临时把服务器日志级别调到 info,再按时间点对照。
最快的单项测试是什么?
在网络外部的机器上执行 openssl s_client -connect example.com:443 -servername example.com -brief,再用 SSL 检测 测一次。两者都成功,问题就在报错的那个客户端或它所在的网络。