Programming languages

Python SSLEOFError: EOF occurred in violation of protocol

By · Last verified

Error messagessl.SSLEOFError: [SSL: UNEXPECTED_EOF_WHILE_READING] EOF occurred in violation of protocol

SSLEOFError: EOF occurred in violation of protocol means the TLS connection was cut off: the other side, or a device in between, closed TCP without finishing TLS. During the handshake that points to a proxy or firewall, a version or cipher mismatch, or a missing SNI name. While reading a response, it means the server closed without close_notify.

Quick fix

A handshake test outside Python shows whether the network, the server or your code is at fault.

Reproduce the handshake outside Python to see who hangs up:

openssl s_client -connect example.com:443 -servername example.com </dev/null
openssl s_client -connect example.com:443 -servername example.com -tls1_2 </dev/null
python3 -c "import ssl; print(ssl.OPENSSL_VERSION)"
env | grep -i _proxy
  • unexpected eof while reading or no peer certificate available with no server certificate: the connection is closed during the handshake. Test from another network and without proxy variables.
  • The handshake only works with -tls1 or -tls1_1: the server is too old for modern Python. Upgrade the server's TLS configuration.
  • The handshake works but your code fails while downloading: see cause 4.

Causes

1. A proxy, firewall or security gateway closes the connection

Corporate gateways may block a destination by SNI and reset the connection. A proxy variable pointing at the wrong port has a similar effect.

  • Confirm: the error disappears on another network, or with NO_PROXY=example.com. In openssl s_client output the server never sends a certificate.
  • Fix: use the correct proxy URL (it normally starts with http://), or ask the network team to allow the destination.

2. The server rejects your TLS version or ciphers by hanging up

Many servers send a protocol alert, but some (old appliances, embedded devices, misconfigured load balancers) close the socket instead. Since Python 3.10, the default context requires TLS 1.2+ and security level 2, which rules out legacy-only servers.

  • Confirm: the SSL checker or openssl s_client -tls1_2 fails, while -tls1 succeeds.
  • Fix: enable TLS 1.2 and TLS 1.3 with modern ciphers on the server. The TLS config generator produces a working Nginx or Apache block.

3. No SNI, or a hostname the server does not know

Servers hosting many sites may close connections that do not carry a matching SNI hostname. Code that calls wrap_socket() without server_hostname=, or that connects by IP address, sends no SNI.

  • Confirm: openssl s_client without -servername fails and with it succeeds.
  • Fix: always pass the hostname:
import socket, ssl
ctx = ssl.create_default_context()
with socket.create_connection(("example.com", 443)) as sock:
    with ctx.wrap_socket(sock, server_hostname="example.com") as tls:
        print(tls.version())

4. The server closes without close_notify after the response

OpenSSL 3 reports a missing close_notify as an explicit error (UNEXPECTED_EOF_WHILE_READING), so depending on how the library reads the body, a download can end with SSLEOFError.

  • Confirm: the handshake succeeds and most of the data arrives; the error is raised at the end of a read.
  • Fix: fix the server or proxy to shut down TLS cleanly. If you cannot, set ssl.OP_IGNORE_UNEXPECTED_EOF (Python 3.10+, OpenSSL 3) on a context used only for that server.

5. Server-side limits

WAFs, CDNs and overloaded backends drop new connections when limits are hit. Mutual-TLS servers can also close the connection when no client certificate is presented.

  • Fix: reuse connections, retry with backoff, and check the server's logs; for mTLS, supply cert= (requests) or load_cert_chain().

Fixes by environment

Find out who hangs up first; the fix is then a proxy setting, a TLS version change or a server limit. OpenSSL tools report the same condition as unexpected eof while reading.

pip: pip install --proxy http://proxy.example.com:8080 package. If a mirror or private index triggers the error, test it with openssl s_client first.

requests: use a Session and mount an HTTPAdapter with max_retries for transient drops; pass proxies= explicitly when environment variables are unreliable.

Docker / CI: confirm the container sees the same proxy variables as your shell, and that its OpenSSL version is the one you expect (python3 -c "import ssl; print(ssl.OPENSSL_VERSION)").

Legacy devices you cannot upgrade: create a separate context for that one host and document why; do not change global defaults.

Don't do this

  • verify=False. It does not address a closed connection and disables certificate checks for everything else.
  • Lowering the minimum TLS version or security level globally (for example DEFAULT:@SECLEVEL=0). That weakens every connection in the process to accommodate one broken server.
  • Swallowing SSLEOFError in a bare except. If it happens mid-download, the data may be truncated; retry or fail loudly.

FAQ

Is SSLEOFError a certificate problem?

No. It means the connection was closed, not that a certificate was rejected. That is why verify=False does not help. Look at what sits between you and the server (proxy, firewall, load balancer) and at the TLS versions and ciphers both sides support.

Why does curl work while Python fails?

They may use different OpenSSL builds, TLS versions, cipher lists and proxy settings. Compare curl -v output with python3 -c "import ssl; print(ssl.OPENSSL_VERSION)", check env | grep -i proxy, and test the exact version with openssl s_client -tls1_2 or -tls1_3.

The error appears only under load. What does that mean?

The server, a WAF or a NAT gateway is probably refusing or dropping excess connections. Reuse connections with a requests.Session, limit concurrency, and add retries with backoff (urllib3's Retry). Check server-side connection limits and rate limiting rules.

Is `ssl.OP_IGNORE_UNEXPECTED_EOF` safe to use?

Only for a specific server that is known to close connections without close_notify, and only when the protocol frames the data (for example HTTP with Content-Length or chunked encoding), so a truncated response is still detected. Apply it to a dedicated context, never globally.

Sources

  1. Python docs: ssl — TLS/SSL wrapper for socket objects
  2. OpenSSL 3.0: openssl-s_client
  3. Requests: Advanced Usage
  4. pip: HTTPS Certificates