Command-line tools
OpenSSL unexpected eof while reading: causes and fixes
error:0A000126:SSL routines::unexpected eof while readingerror: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:
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); PHP shows it inside an "SSL operation failed" warning (see Composer SSL operation failed). If curl hits it during the handshake instead of while reading data, it exits with code 35 (see curl 35).
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:
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 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:
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:
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.
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
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.
FAQ
Is this a certificate problem?
No. The error is about how the connection ended, not about trust. A certificate problem produces a verify error such as unable to get local issuer certificate or certificate has expired. Disabling certificate verification will not change this error.
Why did this start after upgrading to OpenSSL 3?
The underlying event, a peer closing TCP without close_notify, probably happened before too. OpenSSL 1.1.1 reported it as SSL_ERROR_SYSCALL with errno 0, which many programs ignored. OpenSSL 3.0 reports SSL_ERROR_SSL with SSL_R_UNEXPECTED_EOF_WHILE_READING, so the same event now surfaces as an error.
Is it safe to set SSL_OP_IGNORE_UNEXPECTED_EOF?
Only when the protocol on top of TLS detects truncation itself. HTTP/1.1 with Content-Length or chunked encoding does; a protocol that reads until the connection closes does not, and an attacker could cut the stream short without you noticing. OpenSSL's documentation gives the same condition.
Why does openssl s_client show this when I pipe /dev/null into it?
s_client closes its side as soon as stdin reaches EOF, and the server then closes the connection, sometimes without close_notify. The OpenSSL manual recommends -ign_eof for non-interactive use, especially with TLS 1.3. Judge the server by the handshake lines (Protocol, Cipher, Verify return code), not by the last error line.