命令行工具

curl: (35) SSL connect error:TLS 握手失败的排查

作者: · 最后核实

报错信息curl: (35) OpenSSL SSL_connect: SSL_ERROR_SYSCALL in connection to example.com:443

curl: (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 的默认安全级别也不再允许它们。老旧的内网设备、工控系统尤其常见这个问题。

资料来源

  1. libcurl error codes
  2. curl man page
  3. OpenSSL openssl-s_client