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.