# OpenSSL unexpected eof while reading: causes and fixes

> OpenSSL 3 reports unexpected eof while reading when the peer closes TCP without a TLS close_notify. Check whether the handshake or shutdown failed.

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

**`error:0A000126:SSL routines::unexpected eof while reading` means the other side closed the TCP connection without first sending the TLS `close_notify` alert.** Timing matters. A drop during the handshake is a real connectivity problem. A drop after the response usually means a server that closes connections sloppily, and the data may be complete.

## What the error means

The peer closed the TCP connection without the TLS `close_notify` alert. TLS ends a connection with a closure alert. RFC 9846, the current TLS 1.3 specification, says each party must send `close_notify` before closing its write side, unless it already sent an error alert. Without it, the receiver cannot tell whether all data arrived or an attacker cut the stream short (a truncation attack).

OpenSSL 3.0 changed how it reports a missing `close_notify`. Before 3.0, `SSL_read()` returned `SSL_ERROR_SYSCALL` with nothing on the error stack and `errno` 0, and many applications treated that as a normal close. Since 3.0, OpenSSL returns `SSL_ERROR_SSL` with the reason `SSL_R_UNEXPECTED_EOF_WHILE_READING` (code `0A000126`). The event is old; the error message is new.

You will see it in different wrappers:

```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 maps this reason to `SSLEOFError` (see [EOF occurred in violation of protocol](https://ssl.sh/errors/python-ssl-eof-occurred-in-violation-of-protocol/)); PHP shows it inside an "SSL operation failed" warning (see [Composer SSL operation failed](https://ssl.sh/errors/composer-ssl-operation-failed/)). If curl hits it during the handshake instead of while reading data, it exits with code 35 (see [curl 35](https://ssl.sh/errors/curl-35-ssl-connect-error/)).

## Causes, most common first

### 1. The server closes without close_notify after responding

Some servers, frameworks, proxies and load balancers close the TCP socket when they are done, or reset it on an idle timeout. The response was delivered; only the goodbye is missing.

- **Confirm:** `curl -v https://example.com/` prints the full response headers and body before the error. The status code and body are complete.
- **Fix:** if you control the server, update it or its load balancer so connections are shut down gracefully. On the client, ignoring the missing alert is acceptable only when the protocol detects truncation (see below).

### 2. The handshake is dropped instead of rejected

A server that does not like the ClientHello should answer with an alert such as `protocol_version` or `handshake_failure`. Some close the connection instead. Typical triggers: the server supports only old TLS versions or ciphers, the client did not send SNI and the server requires it, or the port is not a TLS endpoint.

- **Confirm:** test versions and SNI explicitly:

```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      # no SNI
```

If one variant completes (shows `Protocol` and `Cipher`) and another fails with this error, you have found the mismatch. The [SSL checker](https://ssl.sh/tools/ssl-checker/) lists the protocol versions and ciphers the server accepts.

- **Fix:** enable TLS 1.2 and 1.3 on the server, and make sure your client sends SNI (for `openssl s_client`, always pass `-servername`).

### 3. A firewall, IPS or proxy cuts the connection

Security devices that inspect TLS, block certain destinations or limit connections often send a TCP reset or silently close. The pattern is that the same command works from another network.

- **Confirm:** compare from a different network, and capture the close:

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

A reset (`Flags [R]`) right after your ClientHello points to a device in the path.

- **Fix:** ask the network team to allow the destination or exempt it from inspection.

### 4. A reused keep-alive connection was already closed

A client that reuses an idle pooled connection after the server's idle timeout gets EOF on the first read.

- **Confirm:** the error is intermittent and appears after quiet periods.
- **Fix:** set the client's idle timeout shorter than the server's or load balancer's, or retry idempotent requests once.

### 5. The server is overloaded or rate limiting

Servers out of workers or connections can close new connections immediately. Check server logs and connection limits at the time of the error.

## Fix commands by platform

Fix the server or the network path when the handshake drops; tolerate a missing `close_notify` on the client only when the protocol detects truncation.

**Server (Nginx):** current Nginx sources set `SSL_OP_IGNORE_UNEXPECTED_EOF` on their SSL contexts, so upgrading an old build removes this noise from Nginx logs. For other servers, update the TLS stack and align idle timeouts between the application, reverse proxy and load balancer.

**Python client (3.10+ with OpenSSL 3), only when the protocol detects truncation:**

```python
import ssl
ctx = ssl.create_default_context()      # verification stays on
ctx.options |= ssl.OP_IGNORE_UNEXPECTED_EOF
```

**Any OpenSSL 3.2+ program**, via a process-specific configuration file (`OPENSSL_CONF=/path/file.cnf`) with `Options = IgnoreUnexpectedEOF` in the `system_default` section. OpenSSL 3.0 and 3.1 do not have this configuration command.

**Windows:** this exact message only comes from programs that use OpenSSL, such as Python from python.org. Programs built on Windows' own Schannel report a dropped connection with different errors; see [Windows Schannel errors](https://ssl.sh/errors/windows-schannel-errors/).

## Don't disable certificate verification

Flags like `curl -k` or `verify=False` do nothing for this error, because the failure is about connection closure, not trust. They only make it possible for anyone on the path to impersonate the server and read your credentials.

Also do not set `SSL_OP_IGNORE_UNEXPECTED_EOF` globally "to make the error go away". OpenSSL's documentation says to enable it only if the protocol on top of TLS can detect truncation and the application checks for it. The safer alternatives are: fix the server or load balancer, align timeouts, retry idempotent requests, and enable the option only in the specific client that speaks a length-delimited protocol such as HTTP/1.1.

## Verify the fix

```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/
```

The first command should show `Protocol`, `Cipher` and `Verify return code: 0 (ok)`; an EOF line printed after those, when s_client closes its own stdin, is not a failure. The second should print an HTTP status and `0` without curl error 56 or 35. More connection tests are collected in the [test connections cookbook](https://ssl.sh/cookbook/test-connections/).
