Problem
You upgraded a server to a newer OS — Ubuntu 22.04, a fresh Debian, anything that ships OpenSSL 3 — and your logs lit up with:
error:0A000126:SSL routines::unexpected eof while reading
or, from curl:
curl: (56) OpenSSL SSL_read: error:0A000126:SSL routines::unexpected eof while reading, errno 0
Nothing in your code changed. The same script, the same mail server, the same API call worked fine on the old box. Now it either floods the log with this line while apparently still working, or it fails outright. Those are two completely different situations wearing the same error message, and the entire fix depends on telling them apart.
Symptoms
- The error started appearing after an OS upgrade or an OpenSSL bump to 3.x, with no change to your own code.
- It shows up in mail server logs (Postfix, Exim), in curl as
(56), in PHP/WHMCS, or in any app that opens outbound TLS. - Sometimes the request still succeeds and this is pure noise; sometimes the connection genuinely fails and nothing comes back.
What it actually means
TLS has a polite way to end a conversation: a close_notify alert that says “I’m done, and I’m telling you so you know the message wasn’t cut off.” The alternative — just dropping the TCP connection — leaves the other side unable to distinguish a normal end from an attacker truncating the stream early.
OpenSSL 1.1.1 mostly ignored a missing close_notify. You’d get a vague SSL_ERROR_SYSCALL with errno 0, or nothing at all. OpenSSL 3.0 reinstated the detection and gave it a name: error:0A000126 ... unexpected eof while reading. So the behavior on the wire is identical to before — a peer hung up without the goodbye — but 3.0 now reports it where 1.1.1 stayed quiet. That single change is why the error “appeared” after an upgrade. The upgrade didn’t break anything; it started telling you about something that was always happening.
That reframes the whole question. The error isn’t the problem. The question is whether the connection worked anyway.
The split: noise vs. a real failure
It’s benign (just noise) when the data got through. Tons of peers close the socket the instant they’re done instead of sending close_notify: mobile clients, mail user agents, some load balancers, older servers. The response arrived, the transfer completed, only the shutdown was impolite. Here there is nothing to fix on your side — and nothing you can fix, since it’s the other end’s manners. If the noise is overwhelming a mail log, that’s a logging-verbosity question, not a TLS one.
It’s a real failure when nothing came back — the handshake never completed, or you got zero bytes. Now unexpected eof is a symptom, and the cause is almost never a certificate:
- You’re speaking TLS to a port that isn’t TLS. The classic: pointing an HTTPS/TLS client at a plaintext service, or the wrong port entirely. The server reads your ClientHello as garbage and closes the socket. Confirm the port actually negotiates TLS before blaming the certificate.
- No shared protocol version or cipher. One side dropped TLS 1.0/1.1 (most modern builds have), the other only offers those. The handshake dies immediately and looks like an abrupt EOF.
- A firewall or load balancer is cutting in. A middlebox sending a RST mid-handshake, an idle-timeout killing the connection, or an LB with no backend produces the same truncated close.
- The server is falling over. Crashing, OOM-killed, or timing out before it finishes the handshake — it disconnects because it died, not because of anything TLS-specific.
The fast way to sort these: does the port speak TLS at all, and do both ends share a version and cipher? If yes and data flows, you’re in noise territory. If the handshake never completes, you’re in failure territory and the problem is the port, the protocol, or a box in the middle.
Diagnose with DechoNet
- SSL Check completes a real TLS handshake against the host and port, so you can see whether the endpoint actually negotiates TLS, which versions and ciphers it offers, and whether a valid certificate is served — the quickest way to confirm “the handshake can complete at all,” which is exactly the line between noise and failure.
- Port Check tells you whether the port is even open and reachable from the outside, separating “the service isn’t listening / a firewall is dropping me” from a TLS-layer problem. A port that’s filtered or closed explains an
unexpected eofwith no certificate involved. - HTTP Check confirms whether an HTTPS endpoint returns a real response, which distinguishes a working connection that merely closes rudely (benign) from one that never delivers anything (the failure case worth chasing).
Resolution Checklist
- Confirm whether the request actually succeeded. If data came back and only the close was unclean, it’s noise — stop here.
- If it failed, verify the port actually speaks TLS (you’re not pointing a TLS client at a plaintext or wrong port).
- Check that client and server share a TLS version — a modern client won’t talk to a server stuck on TLS 1.0/1.1, and vice versa.
- Rule out a firewall / load balancer sending a RST or timing out mid-handshake, and a server that’s crashing before it responds.
- Do not paper over it with
-k/--insecureorNODE_TLS_REJECT_UNAUTHORIZED=0; that disables verification without touching the actual cause. - If it’s only log noise from well-behaved-but-impolite peers, treat it as a logging level decision, not a bug.
When to Escalate
- If the error is benign noise flooding a mail server log, the “fix” is on the peers’ side (they should send
close_notify) and out of your hands — manage it with log filtering rather than chasing a TLS bug that isn’t there. - If the handshake genuinely won’t complete and the port, protocol, and certificate all check out, capture the exchange with
openssl s_client -connect host:port(or a packet capture) to see exactly where the peer hangs up — that pinpoints a middlebox or a server-side crash. - If the failure is specifically a certificate validation problem rather than an abrupt close, you’re looking at a different error family — see curl: (60) SSL certificate problem for the server-chain-vs-client-trust split.
Check your own domain now
Free, no sign-up. Runs the exact check this guide describes and shows what to fix.