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

> curl 报错 35 表示 TLS 握手本身失败：协议或加密套件不匹配、连接被重置、端口不是 TLS、或者服务器要求客户端证书。按报错后半句定位原因。

Source: https://ssl.sh/zh/errors/curl-35-ssl-connect-error/
Last updated: 2026-11-10

**`curl: (35)` 的意思是：TLS 握手失败了，curl 甚至还没来得及检查证书。** 后面的 OpenSSL 文字是线索：连接被重置、端口不是 TLS、协议或加密套件不匹配，或者服务器要求客户端证书。

## 快速修复

35 的具体原因写在 OpenSSL 的后半句里。先用详细模式拿到完整的握手过程：

```bash
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 检测工具](https://ssl.sh/zh/tools/ssl-checker/)可以看到服务器支持哪些协议版本。

## 原因

curl 35 最常见的原因是握手中途连接被重置。

### 1. 握手过程中连接被重置

- **确认：** `curl -v` 在 `Client hello` 之后就停了；`openssl s_client` 打印 `errno=104` 或 `unexpected eof`。
- **修复：** 查服务器前面有什么：对未知 SNI 直接断开的防火墙或负载均衡、没有该域名证书的 server 块（见 [Nginx no ssl_certificate defined](https://ssl.sh/zh/errors/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 配置生成器](https://ssl.sh/zh/tools/tls-config-generator/)能生成当前推荐配置。实在改不了的老设备，把它隔离起来，给访问它的那个客户端单独配置，而不是全局降级。

### 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](https://ssl.sh/zh/errors/curl-60-ssl-certificate-problem/)。

## 不要这样做

加 `-k`、降低安全级别、强制旧版 TLS 都不是修法：要么无效，要么削弱整台机器的 TLS 安全。

- **加 `curl -k`。** 对 35 没用，失败发生在证书检查之前。
- **全局降低 OpenSSL 安全级别**（在系统配置里写 `CipherString = DEFAULT@SECLEVEL=0`）。整台机器的所有 TLS 连接都会被削弱。
- **在脚本里强制 `--tlsv1.0`。** 掩盖了需要升级的服务器，还让流量暴露在已知的协议弱点下。
