Geonode logo
Library and script errors

How to Fix ChunkedEncodingError in Python requests

Find where the body gets cut, then retry or resume the download with code for Python, Node.js and cURL.

Updated

TL;DR

ChunkedEncodingError means Python requests got the response headers, but the connection closed before the body was complete, so the read broke partway. The fastest fix is to catch the error and send the GET again, resuming large files with a Range header instead of starting over.

What a broken body looks like in each client

Byte counts show where the body stopped, so yours will differ.

WhereWhat you see
Python requests with urllib3 2.x, cut inside a chunkrequests.exceptions.ChunkedEncodingError: ('Connection broken: IncompleteRead(500 bytes read, 524 more expected)', IncompleteRead(500 bytes read, 524 more expected))
Python requests with urllib3 2.x, cut between chunksrequests.exceptions.ChunkedEncodingError: Response ended prematurely
Python requests with urllib3 1.26, cut between chunksrequests.exceptions.ChunkedEncodingError: ("Connection broken: InvalidChunkLength(got length b'', 0 bytes read)", InvalidChunkLength(got length b'', 0 bytes read))
pip 24.0 and 26.1, package download cut (after ERROR: Exception:)pip._vendor.urllib3.exceptions.ProtocolError: ('Connection broken: IncompleteRead(500 bytes read, 524 more expected)', IncompleteRead(500 bytes read, 524 more expected))
httpx 0.27httpx.RemoteProtocolError: peer closed connection without sending complete message body (incomplete chunked read)
Node.js 20 and 24 fetch(), reading the bodyTypeError: terminated ... [cause]: SocketError: other side closed
curlcurl: (18) transfer closed with outstanding read data remaining

Why this happens

The connection closed before the response body reached its end.

A chunked body ends with a zero-size chunk, and a Content-Length body ends at its stated size. When the server, a proxy or the network closes the connection sooner, urllib3 flags the read as broken.

Diagnose your ChunkedEncodingError first

Each check narrows down where the body gets cut: at the site, on the route or in your own loop.

  • Download the same URL in a browser on this computer; if it also stops partway, the site or your connection ends it, not your script.

  • Run curl --http1.1 -sS -o /dev/null with the same proxy and URL; curl: (18) means the body is cut before it ever reaches Python.

  • Repeat the download once through the proxy and once without it; if only the proxied run breaks, the drop happens on the proxy route.

  • Compare byte counts in the error across runs; an identical count each time means the server stops at one point, so retries will not help.

  • Log the seconds between chunks in your iter_content loop; if errors follow long gaps, the server stopped waiting for a slow reader.

Solutions ranked by effectiveness

Start with a plain retry; the other two cover large files and loops that read slowly.

  1. Most common fix

    Catch the error and request again

    Applies when cuts come and go. Catch ChunkedEncodingError, wait a little longer each time and repeat the GET on a new connection; curl repeats its error 18 only with --retry-all-errors.

    # pip install requests
    import time
    import requests
    proxy = "http://USERNAME:PASSWORD@proxy.geonode.io:9000"
    for attempt in range(1, 5):
        try:
            r = requests.get("https://example.com/data.json", proxies={"http": proxy, "https": proxy}, timeout=60)
            print(r.status_code, len(r.content), "bytes")
            break
        except requests.exceptions.ChunkedEncodingError:
            if attempt == 4: raise
            time.sleep(2 ** attempt)
  2. Check next

    Resume large files from the last byte

    Applies to big downloads that break after many megabytes. When the response carries Accept-Ranges: bytes, ask only for the missing part instead of fetching the whole file again.

    1. After a cut, send the GET again with Range: bytes=<bytes already saved>-.

    2. Append the new body to your file only if the answer is 206 Partial Content.

    3. If the server answers 200, it ignored Range; rewrite the file from the start.

    4. With curl, use -C -; before curl 8.17.0, --retry restarts it, so rerun instead.

  3. For developers

    Keep the read loop moving

    Applies when cuts follow slow work between chunks. A server stops sending to a client that takes nothing for too long; nginx's send_timeout, for example, defaults to 60 seconds.

    1. Write each chunk to disk inside the loop and process the file afterwards.

    2. Move parsing, database writes and API calls out of the iter_content loop.

    3. When work must happen live, put chunks on a queue that another thread drains.

Stop the ChunkedEncodingError from coming back

Habits that catch short downloads early and keep a cut file from passing as finished.

  1. Save to a .part file and rename it only after the last chunk arrives.

  2. Check the file's size or hash against the value the publisher lists before you use it.

  3. Pass a read timeout such as timeout=(10, 60), so a stalled body fails instead of hanging.

  4. Log the inner exception's class and byte counts per URL, so hosts that cut often stand out.

Resume instead of re-downloadingOn per-GB residential plans, a Range resume uses traffic only for the missing bytes.
Try residential proxies

Related errors

Learn more

FAQ

Python requests stopped receiving the reply partway through and could not finish it. The class calls itself an invalid chunk from the server, yet requests raises it for every urllib3 ProtocolError during body reads.

urllib3 expected more data in the current chunk or under Content-Length when the socket closed. In IncompleteRead(500 bytes read, 524 more expected), 524 is what never arrived.

urllib3 1.26 tried to read the next chunk-size line and got nothing, because the connection closed between chunks; urllib3 2.x words the same cut as Response ended prematurely. Other bytes in place of b'' mean a malformed chunk.

requests asks urllib3 for the headers only and reads the body later, outside urllib3's retry loop. In our test, a session mounted with Retry(total=5, read=5) still sent one request and raised ChunkedEncodingError.

Chrome shows it when a chunked reply stops before its end marker, the same failure requests reports. A reply shorter than its Content-Length gets ERR_CONTENT_LENGTH_MISMATCH instead.

Yes, with urllib3 1.26: our test got status 200 and 1,000 of 100,000 promised bytes, with no exception. urllib3 2.0 made enforce_content_length default to True, so that cut now raises ChunkedEncodingError.

One exit IP per download

Sticky ports keep one IP for a lifetime you set, 3 minutes to 24 hours.