# Python SSLEOFError: EOF occurred in violation of protocol

> SSLEOFError means the TLS connection was cut off mid-handshake or closed without close_notify. Check proxies, SNI, TLS versions and server limits.

Source: https://ssl.sh/errors/python-ssl-eof-occurred-in-violation-of-protocol/
Last updated: 2026-11-10

**`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](https://ssl.sh/glossary/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:

```bash
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](https://ssl.sh/tools/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](https://ssl.sh/tools/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:

```python
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](https://ssl.sh/errors/openssl-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.
