命令行工具
curl: (35) SSL connect error:TLS 握手失败的排查
curl: (35) OpenSSL SSL_connect: SSL_ERROR_SYSCALL in connection to example.com:443curl: (35) 的意思是:TLS 握手失败了,curl 甚至还没来得及检查证书。 后面的 OpenSSL 文字是线索:连接被重置、端口不是 TLS、协议或加密套件不匹配,或者服务器要求客户端证书。
快速修复
35 的具体原因写在 OpenSSL 的后半句里。先用详细模式拿到完整的握手过程:
curl -v https://example.com/ 2>&1 | sed -n '1,30p'
openssl s_client -connect example.com:443 -servername example.com -brief </dev/null
然后对照报错文字:
| 报错文字 | 含义 |
|---|---|
SSL_ERROR_SYSCALL、Connection reset by peer |
握手中途连接被断开 |
wrong version number |
端口说的不是 TLS |
handshake failure、no protocols available、unsupported protocol |
没有共同支持的 TLS 版本或加密套件 |
tlsv13 alert certificate required,或服务器请求证书后 alert handshake failure |
需要客户端证书 |
unsafe legacy renegotiation disabled |
很老的服务器不支持安全重协商 |
用 SSL 检测工具可以看到服务器支持哪些协议版本。
原因
curl 35 最常见的原因是握手中途连接被重置。
1. 握手过程中连接被重置
- 确认:
curl -v在Client hello之后就停了;openssl s_client打印errno=104或unexpected eof。 - 修复: 查服务器前面有什么:对未知 SNI 直接断开的防火墙或负载均衡、没有该域名证书的 server 块(见 Nginx no ssl_certificate defined)、需要设置
HTTPS_PROXY的代理。换一个网络(比如手机热点)测试,区分是本地网络设备的问题还是服务器的问题。国内云服务器上未备案域名的 443 访问被拦截,也会表现为连接被重置。
2. 端口说的不是 TLS
- 确认: 报错是
wrong version number;curl http://example.com:443/能拿到 HTTP 响应。 - 修复: 用对协议和端口,或者给该监听开启 TLS(
listen 443 ssl;)。把明文代理写成https://也会出这个错:HTTPS_PROXY应该写http://proxy:3128。
3. 没有共同支持的协议版本或加密套件
较新的 curl 通过 OpenSSL 的安全级别默认要求 TLS 1.2 以上,而老服务器、嵌入式设备、老版本 IIS 可能只支持 TLS 1.0/1.1 或弱加密套件。
- 确认:
openssl s_client -tls1_2 ...失败,而在仍允许旧协议的测试环境里-tls1能连上。 - 修复: 升级服务器的 TLS 配置,TLS 配置生成器能生成当前推荐配置。实在改不了的老设备,把它隔离起来,给访问它的那个客户端单独配置,而不是全局降级。
4. 服务器要求客户端证书
银行、支付、政务接口常要求双向认证。
- 确认:
openssl s_client输出里有Acceptable client certificate CA names,随后握手失败。 - 修复: 带上证书和私钥:
curl --cert client.pem --key client.key https://example.com/。
5. 老服务器不支持安全重协商
OpenSSL 3 会拒绝不支持 RFC 5746 安全重协商的服务器,报 unsafe legacy renegotiation disabled。
- 修复: 升级服务器。临时办法是只给这一个客户端用一份带
Options = UnsafeLegacyServerConnect的 OpenSSL 配置。
各环境修法
各环境的差别主要在 curl 所用的 TLS 库和中间的代理:先用 curl -V 看清 TLS 库,再查代理设置。
Windows(Schannel): 报错形如 schannel: next InitializeSecurityContext failed。如果提到吊销检查,让代理放行 CRL/OCSP 地址,或用 --ssl-revoke-best-effort。
macOS: 系统 curl 和 Homebrew curl 用的 TLS 库不同,一个能用一个不能用时,用 curl -V 对比。
Docker: 精简镜像里的 curl 可能缺少部分协议支持,安装发行版完整的 curl 包。
公司代理环境: 设置 HTTPS_PROXY=http://proxy.example.com:3128(注意是 http://),并信任代理 CA,否则会接着遇到 报错 60。
不要这样做
加 -k、降低安全级别、强制旧版 TLS 都不是修法:要么无效,要么削弱整台机器的 TLS 安全。
- 加
curl -k。 对 35 没用,失败发生在证书检查之前。 - 全局降低 OpenSSL 安全级别(在系统配置里写
CipherString = DEFAULT@SECLEVEL=0)。整台机器的所有 TLS 连接都会被削弱。 - 在脚本里强制
--tlsv1.0。 掩盖了需要升级的服务器,还让流量暴露在已知的协议弱点下。
常见问题
curl 报错 35 和 60 有什么区别?
35 是握手没完成;60 是握手完成了,但证书验证不通过。60 靠修 CA 证书包或证书链解决;35 要查协议版本、加密套件、端口和网络设备。
Windows 上看到 schannel: next InitializeSecurityContext failed,是同一个错吗?
是的,这是 Windows Schannel 后端下的错误 35。常见的一种提示是“吊销功能无法检查证书是否吊销”,多发生在公司代理拦截了 CRL/OCSP 地址时。让代理放行这些地址,或者用 --ssl-revoke-best-effort,只在确认已吊销时才失败。
强制用旧版 TLS 能解决吗?
不能,旧版 TLS 只适合用来排查。服务器如果最高只支持 TLS 1.1,应该升级服务器:RFC 8996 已经废弃 TLS 1.0 和 1.1,当前 OpenSSL 的默认安全级别也不再允许它们。老旧的内网设备、工控系统尤其常见这个问题。