# unexpected eof while reading：OpenSSL 3 连接被提前关闭

> OpenSSL 3 报 unexpected eof while reading，说明对方没发 TLS close_notify 就断开了 TCP。先分清是握手阶段被断开，还是数据传完后没正常关闭。

Source: https://ssl.sh/zh/errors/openssl-unexpected-eof-while-reading/
Last updated: 2026-11-10

**`error:0A000126:SSL routines::unexpected eof while reading` 的意思是：对方没有先发送 TLS 的 `close_notify` 告警，就直接关闭了 TCP 连接。** 关键是看它发生在什么时候：如果在握手阶段就断了，说明服务器或中间设备拒绝了你，这是连通性问题；如果服务器已经把响应发完才断，通常只是对方关连接的方式不规范，数据可能是完整的。

## 这条报错是什么意思

这条报错说明对方没按 TLS 规范发 `close_notify` 就关了连接，OpenSSL 3.0 起把这种情况报成错误。TLS 用“关闭告警”来结束连接。现行的 TLS 1.3 规范 RFC 9846 规定，除非已经发过错误告警，每一方在关闭写方向之前都必须发送 `close_notify`。没有这个告警，接收方就无法判断数据是全部到了，还是被攻击者中途截断了（截断攻击）。

OpenSSL 3.0 改变了“缺少 close_notify”的报告方式。3.0 之前，`SSL_read()` 返回 `SSL_ERROR_SYSCALL`，错误栈为空，`errno` 为 0，很多程序把它当成正常关闭。3.0 起改为返回 `SSL_ERROR_SSL`，原因是 `SSL_R_UNEXPECTED_EOF_WHILE_READING`（错误码 `0A000126`）。事情本身不新鲜，新的是这条报错。

它会以不同的样子出现：

```text
curl: (56) OpenSSL SSL_read: error:0A000126:SSL routines::unexpected eof while reading, errno 0
ssl.SSLEOFError: [SSL: UNEXPECTED_EOF_WHILE_READING] EOF occurred in violation of protocol (_ssl.c:1006)
```

Python 把这个原因映射成 `SSLEOFError`（见 [EOF occurred in violation of protocol](https://ssl.sh/zh/errors/python-ssl-eof-occurred-in-violation-of-protocol/)）；PHP 会把它放在 “SSL operation failed” 警告里（见 [Composer SSL operation failed](https://ssl.sh/zh/errors/composer-ssl-operation-failed/)）。如果 curl 是在握手阶段而不是读数据时遇到它，退出码会是 35（见 [curl 35](https://ssl.sh/zh/errors/curl-35-ssl-connect-error/)）。

## 常见原因（按出现频率排序）

最常见的是服务器响应完直接断开、不发 close_notify，这时数据通常是完整的。

### 1. 服务器响应完直接断开，不发 close_notify

有些服务器、框架、代理和负载均衡处理完请求就直接关闭 TCP，或者在空闲超时时发 RST。响应已经送达，只是少了“告别”。

- **确认：** `curl -v https://example.com/` 在报错之前已经打印出完整的响应头和响应体，状态码和内容都正常。
- **修复：** 服务器归你管就升级服务器或负载均衡，让它正常关闭连接。客户端忽略这个告警，只在上层协议能检测截断时才可以（见下文）。

### 2. 握手被直接掐断，而不是正常拒绝

服务器不接受 ClientHello 时，本应回一个 `protocol_version` 或 `handshake_failure` 告警，有些服务器却直接关连接。常见原因：服务器只支持老旧的 TLS 版本或加密套件；客户端没发 SNI 而服务器要求 SNI；这个端口不是 TLS 服务。

- **确认：** 分别测试协议版本和 SNI：

```bash
openssl s_client -connect example.com:443 -servername example.com -tls1_3 </dev/null
openssl s_client -connect example.com:443 -servername example.com -tls1_2 </dev/null
openssl s_client -connect example.com:443 </dev/null      # 不带 SNI
```

某一种能完成握手（显示 `Protocol` 和 `Cipher`），另一种报这个错误，就找到了不匹配的地方。[SSL 检测工具](https://ssl.sh/zh/tools/ssl-checker/)可以列出服务器接受的协议版本和加密套件。

- **修复：** 服务器开启 TLS 1.2 和 1.3；客户端要发送 SNI（用 `openssl s_client` 时要带 `-servername`）。

### 3. 防火墙、IPS 或代理切断了连接

做 HTTPS 检查、拦截特定目标或限制连接数的安全设备，经常直接发 TCP RST 或静默关闭。典型表现是换一个网络同样的命令就正常。

- **确认：** 换网络对比，并抓包看是谁关的连接：

```bash
sudo tcpdump -nn -i any host 203.0.113.10 and port 443
```

ClientHello 之后马上出现 RST（`Flags [R]`），说明是链路上的设备干的。

- **修复：** 请网络管理员放行目标地址，或者把它加入 HTTPS 检查的白名单。

### 4. 复用了已经被关闭的长连接

客户端在服务器空闲超时之后复用连接池里的旧连接，第一次读取就会遇到 EOF。

- **确认：** 报错是间歇性的，而且常出现在一段空闲之后。
- **修复：** 客户端的空闲超时设得比服务器、负载均衡更短，或者对幂等请求重试一次。

### 5. 服务器过载或限流

服务器工作进程、连接数用完时，可能一建立连接就关闭。对照报错时间查看服务器日志和连接数限制。

## 分平台修复命令

服务器端的修法是正常关闭连接；客户端只能在上层协议能检测截断时忽略这个错误。

**服务器（Nginx）：** 当前 Nginx 源码会在 SSL 上下文里设置 `SSL_OP_IGNORE_UNEXPECTED_EOF`，升级老版本 Nginx 就能消除日志里的这类噪音。宝塔面板用户可以在软件商店里升级 Nginx。其他服务器则升级 TLS 库，并让应用、反向代理、负载均衡三者的空闲超时保持一致（客户端侧最短）。

**Python 客户端（3.10+，OpenSSL 3），仅限上层协议能检测截断时：**

```python
import ssl
ctx = ssl.create_default_context()      # 证书校验保持开启
ctx.options |= ssl.OP_IGNORE_UNEXPECTED_EOF
```

**任何 OpenSSL 3.2 及以上的程序：** 用一个只给该进程用的配置文件（`OPENSSL_CONF=/path/file.cnf`），在 `system_default` 段里写 `Options = IgnoreUnexpectedEOF`。OpenSSL 3.0 和 3.1 没有这个配置项。

**Windows：** 这条报错只会出现在使用 OpenSSL 的程序里，比如 python.org 的 Python。使用 Windows 自带 Schannel 的程序会以别的报错表示连接被断开，见 [Windows Schannel 报错](https://ssl.sh/zh/errors/windows-schannel-errors/)。

**国内服务器排查要点：** 访问境外服务时，跨境链路丢包或被中间设备重置都会表现为这个错误，可以先在同地域的另一台云服务器上复现，排除本机问题；云厂商安全组、云防火墙的连接数限制也值得查一下。

## 不推荐：关闭证书校验

`curl -k`、`verify=False` 这类参数对这个错误没有作用，因为问题出在连接关闭而不是信任上。它们唯一的效果是让链路上的任何人都能冒充服务器、读走你的账号密码。

同样不要为了“让报错消失”而全局设置 `SSL_OP_IGNORE_UNEXPECTED_EOF`。OpenSSL 文档要求只在上层协议能检测截断、并且应用确实做了检查时才启用。更安全的做法是：修服务器或负载均衡、对齐超时设置、对幂等请求重试，只在使用定长协议（如 HTTP/1.1）的特定客户端里打开这个选项。

## 怎么确认已经修好

修好后，握手能完成并显示 `Verify return code: 0 (ok)`，curl 也不再报 56 或 35 错误。

```bash
openssl s_client -connect example.com:443 -servername example.com -verify_return_error </dev/null
curl -sS -o /dev/null -w '%{http_code} %{ssl_verify_result}\n' https://example.com/
```

第一条命令应显示 `Protocol`、`Cipher` 和 `Verify return code: 0 (ok)`；如果这些之后因为 s_client 关闭了自己的标准输入而多出一行 EOF，不算失败。第二条命令应输出 HTTP 状态码和 `0`，不再出现 curl 56 或 35 错误。更多连接测试命令见[连接测试速查](https://ssl.sh/zh/cookbook/test-connections/)。
