httpx RemoteProtocolError: Server disconnected without sending a response (tested)

httpx.RemoteProtocolError: Server disconnected without sending a response. means the server closed the connection after reading your request and before answering. It is a server/proxy-side problem; check its logs, and retry only idempotent requests.

httpx.RemoteProtocolError means the server broke the HTTP protocol: it closed the connection early or sent something that is not valid HTTP. The message tells you which. Measured with httpx 0.28.1 on Python 3.13.5 using tiny raw-socket servers that misbehave on purpose.

1. "Server disconnected without sending a response."

import httpx, socket, threading

# a server that reads the request and closes the connection without answering
srv = socket.create_server(("127.0.0.1", 8963))
def serve():
    while True:
        conn, _ = srv.accept()
        conn.recv(65536)
        conn.close()
threading.Thread(target=serve, daemon=True).start()

try:
    httpx.get("http://127.0.0.1:8963/")
except httpx.RemoteProtocolError as e:
    print(type(e).__name__ + ":", e)
    print("is TransportError:", isinstance(e, httpx.TransportError))

Output:

RemoteProtocolError: Server disconnected without sending a response.
is TransportError: True

The server accepted the connection, read the request and closed the socket without writing anything. Typical real causes: the server process crashed or was restarted mid-request, a proxy/load balancer dropped the connection, or the server rejected the request at a lower level. Check the server-side logs for that moment; on the client, it is a TransportError, so it can be caught together with connect errors and retried for idempotent requests.

2. "peer closed connection without sending complete message body"

import httpx, socket, threading

# answers HTTP/1.1 but sends a Content-Length larger than the body, then closes
srv = socket.create_server(("127.0.0.1", 8964))
def serve():
    while True:
        conn, _ = srv.accept()
        conn.recv(65536)
        conn.sendall(b"HTTP/1.1 200 OK\r\nContent-Length: 100\r\n\r\nonly 18 bytes here")
        conn.close()
threading.Thread(target=serve, daemon=True).start()

try:
    httpx.get("http://127.0.0.1:8964/")
except httpx.RemoteProtocolError as e:
    print(type(e).__name__ + ":", e)

Output:

RemoteProtocolError: peer closed connection without sending complete message body (received 18 bytes, expected 100)

The headers promised 100 bytes (Content-Length) but the connection closed after 18. The response was cut off: a crashed upstream, a timeout in a proxy, or a server that sets a wrong Content-Length.

3. "illegal status line"

import httpx, socket, threading
srv = socket.create_server(("127.0.0.1", 8965))
def serve():
    while True:
        conn, _ = srv.accept()
        conn.recv(65536)
        conn.sendall(b"this is not http\r\n\r\n")
        conn.close()
threading.Thread(target=serve, daemon=True).start()
try:
    httpx.get("http://127.0.0.1:8965/")
except httpx.RemoteProtocolError as e:
    print(type(e).__name__ + ":", e)

Output:

RemoteProtocolError: illegal status line: bytearray(b'this is not http')

The first line back was not HTTP/1.1 200 OK-style. Something other than an HTTP server is listening on that port (a database, an SSH server, a custom TCP service).

4. http:// to an HTTPS port is a different error

import httpx
from srv import start
base = start()          # an HTTPS server
print(base)
try:
    httpx.get(base.replace("https://", "http://"))   # plain http to the TLS port
except httpx.TransportError as e:
    print(type(e).__name__ + ":", e)

Output:

https://127.0.0.1:8965
ReadError: [Errno 104] Connection reset by peer

Sending plain HTTP to a TLS port gave httpx.ReadError: [Errno 104] Connection reset by peer here, not RemoteProtocolError; still worth checking the scheme and port when you see either.

Related: httpx retry, httpx timeout, httpx vs requests, httpx CERTIFICATE_VERIFY_FAILED.

Frequently asked questions

What causes httpx 'Server disconnected without sending a response'?

The server closed the connection after receiving the request without writing a response; reproduced with a socket server that reads and closes.

What does 'peer closed connection without sending complete message body' mean?

The response was cut off before Content-Length bytes arrived: received 18 bytes, expected 100 in the test.

Is RemoteProtocolError a TransportError?

Yes, in httpx 0.28.1 isinstance(e, httpx.TransportError) is True.

Written with AI assistance. Measured on 2026-10-23 with httpx 0.28.1 on Python 3.13.5 against local raw-socket and http.server test servers. Each block was run verbatim and the output pasted unedited. PlainHeaders is our extension; httpx is free.