Distributed systems

Incomplete chunked encoding — a 200 status is not a complete response

Written and reviewed by Sahil Srivastav

HTTP/1.1StreamingResponse integrity
net::ERR_INCOMPLETE_CHUNKED_ENCODING

What this error actually means

The client started receiving a chunked HTTP/1.1 response but did not receive a valid completed transfer. Chunked framing carries a sequence of length-prefixed chunks and a terminating zero-sized chunk, followed by any trailers and their ending delimiter. Closing the connection before completion is a truncated message, even when every application record received so far looks plausible.

The status line arrives before the body. A server can send 200 OK, stream several megabytes and then crash. It cannot retract that status, and a proxy cannot replace bytes already delivered with a fresh error response. This is why a status-only uptime check can report success while customers download corrupt archives or incomplete exports.

The browser error shown here is specific to its HTTP framing observation. HTTP/2 does not use HTTP/1.1 chunked transfer coding and can report a reset or another stream error instead. A proxy can translate protocols between hops, so identify which leg used which protocol before applying a chunked-encoding explanation to every interrupted download.

Causes, most common first

  1. 1The producer fails after committing headers. An exception while serialising a later row, a worker kill or a failed storage read ends the response halfway through. The normal error middleware cannot send a new JSON error once headers and body bytes are already on the wire.
  2. 2An intermediary interrupts a quiet stream. The response starts promptly but then goes silent during a slow computation or downstream read. An inactivity timeout closes the exchange after the successful status, leaving the client with an incomplete transfer.
  3. 3Manual framing or header mutation is incorrect. Application code writes chunk sizes itself while the server also manages transfer coding, or middleware changes the body without updating its framing metadata. Compression and response filters can make these bugs appear only for certain content sizes.
  4. 4Deployment or client-path interruption cuts an active response. A forced shutdown, intermediary reset or network loss closes the stream. The chunk parser detects missing completion but cannot tell you which machine caused the loss; correlate network and process evidence.

When you see it

  • DevTools shows a 200 response whose body fails partway through download.
  • Small responses work while long exports or streamed JSON fail intermittently.
  • Downloaded files are shorter than expected or fail archive/checksum validation.
  • nginx logs a premature upstream close or read timeout after response headers were sent.

How to diagnose it

Step 1

Require curl to consume the whole body

Force HTTP/1.1 to inspect this framing path and use a non-sensitive staging export. Preserve curl’s exit status; getting headers is insufficient. A nonzero transfer result means the saved file must not be published as a completed download.

curl --http1.1 -v --max-time 120 -D /tmp/export-headers.txt -o /tmp/export-body.bin https://staging.example.com/export

Step 2

Compare raw framing on a small controlled response

The raw option keeps transfer coding visible. Use a tiny fixture because raw bodies and traces can contain sensitive data and large output. Check the advertised transfer mode before assuming an absent chunk terminator is the relevant failure.

curl --http1.1 --raw --max-time 15 https://staging.example.com/test/stream -o /tmp/stream-framing.bin

Step 3

Replay directly against the selected origin

Use the same request parameters and Host header from the proxy environment. If the direct body also truncates, inspect producer exceptions and worker exits. If only the public path fails, compare inactivity limits, buffering and response transformations hop by hop.

Step 4

Find the last successfully produced record

Correlate the cut position with application iteration logs or a deterministic fixture. Repeated failure at one record suggests serialisation or data handling; variable cut positions at one elapsed time suggest a timer; cut positions aligned with deploys suggest lifecycle handling.

The fix

Let the HTTP library own transfer framing. Do not manually set Transfer-Encoding or write chunk boundaries unless you are implementing an HTTP server correctly at that level. Middleware that compresses or transforms content must leave framing consistent with the bytes actually transmitted.

Validate inputs and establish required resources before starting the response, then handle errors inside the streaming loop. After headers are sent, terminate a failed stream explicitly and log the operation identifier; do not append an unrelated JSON error to an archive or pretend the partial result is complete.

For valuable large exports, generate a durable object first and serve a completed object with integrity metadata, or provide a defined resumable format. Clients should download to a temporary destination and promote it only after successful transfer and any application checksum or manifest validation.

Fix the specific timeout or shutdown boundary when the stream is healthy but interrupted. Ensure intentional heartbeats are flushed through the proxy, and define a maximum stream lifetime. During deploys, drain supported streams or return a resumable checkpoint rather than abruptly killing their worker.

How to stop it coming back

  • Test failures after several emitted records, not only failures before headers; assert that clients reject the partial output.
  • Track completed transfers, bytes and integrity failures separately from initial HTTP statuses.
  • Run export checks through the public protocol path because compression, proxies and HTTP-version translation can alter behaviour.

Practise production debugging in a real repository

Reading about a failure and reproducing one are different skills. Gronex ships broken backend repositories with failing test suites that encode the real invariant, so you debug from evidence instead of memorising symptoms.

FAQ

Can disabling chunked encoding repair a crashed stream?

No. Content-Length framing would still be incomplete if fewer bytes arrive than advertised. Changing transfer coding can change the error text while leaving the same producer crash or connection loss.

Why does the JSON sometimes parse despite the transfer error?

Application content and HTTP framing are different layers. A complete-looking JSON value might arrive before the final transport delimiter is lost. Treat the HTTP transfer as failed unless your application explicitly defines a safe, independently verified recovery rule.

Can I resume any truncated response with Range?

No. Resume requires a stable representation and server support for byte ranges or an application cursor. A dynamically generated export may change between attempts; mixing bytes from different versions can create silent corruption.

Related

Other errors engineers hit next to this one

Full error and symptom index →