Content-Length mismatch errors (tested)
A wrong Content-Length header causes truncated responses, hanging requests and errors such as curl (18) or net::ERR_CONTENT_LENGTH_MISMATCH. We sent eight broken responses from a raw Node server to four clients and wrote down exactly what each one did.
1. The results
The body was always the 11 bytes hello world. "OK" means the client returned without error; the quoted text is the body it gave back.
| Response sent | curl 8.14.1 | Node 24 fetch (undici) | Python requests 2.32.3 | Chromium 153 fetch() / navigation |
|---|---|---|---|---|
| Content-Length: 5 (smaller than body) | OK, "hello" (exit 0) | OK, "hello" | OK, "hello" | OK, "hello" / page shows "hello" |
| Content-Length: 20, server closes after 11 bytes | curl: (18) end of response with 9 bytes missing | TypeError: terminated, cause UND_ERR_SOCKET "other side closed" | ChunkedEncodingError: "Connection broken: IncompleteRead(11 bytes read, 9 more expected)" | TypeError: Failed to fetch / navigation status 200, then net::ERR_CONTENT_LENGTH_MISMATCH |
| Content-Length: 20, connection left open | Hangs; with -m 3: curl: (28) Operation timed out after 3002 milliseconds with 11 out of 20 bytes received | Hangs until our 3 s AbortSignal timeout (TimeoutError) | Hangs until timeout: ConnectionError "Read timed out." | fetch() hangs until our abort / navigation did not finish within 4 s |
| Two identical lines: Content-Length: 11 twice | OK, full body | TypeError: fetch failed, cause UND_ERR_RES_CONTENT_LENGTH_MISMATCH "Response body length does not match content-length header" | OK, full body | OK, full body (both) |
| Two different values: 11 and 5 | OK, but body cut to "hello" (verbose: "Excess found writing body") | UND_ERR_RES_CONTENT_LENGTH_MISMATCH (same as above) | InvalidHeader: "Content-Length contained multiple unmatching values (11, 5)" | TypeError: Failed to fetch / net::ERR_RESPONSE_HEADERS_MULTIPLE_CONTENT_LENGTH |
| Content-Length: 5 plus Transfer-Encoding: chunked (valid chunked body) | OK, full body (chunked used) | UND_ERR_RES_CONTENT_LENGTH_MISMATCH | OK, full body | OK, full body (both) |
| Content-Length: -1 | curl: (8) Invalid Content-Length: value | TypeError: fetch failed: "Response does not match the HTTP/1.1 protocol (Invalid character in Content-Length)" | OK, full body (read until close) | OK, full body (both) |
| Content-Length: abc | curl: (8) Invalid Content-Length: value | Same "Invalid character in Content-Length" error | OK, full body (read until close) | OK, full body (both) |
The Node fetch result for two identical Content-Length lines surprised us: every other client accepted it, but undici in Node 24.21.0 rejected it with a length mismatch error. If a proxy or framework in front of your Node code duplicates the header, Node fetch clients can fail while browsers and curl work.
2. What this means in practice
- Too small is silent. No client we tested reported an error when Content-Length was smaller than the body. They all returned the first N bytes and dropped the rest. A truncated JSON response then fails later as a parse error, far from the real cause.
- Too large is loud only if the connection closes. If the server closes the socket, every client errored. If the server keeps the connection open, every client waited for the missing bytes until a timeout. That looks like a slow or hung endpoint, not a header bug.
- Conflicting values are the riskiest. Clients disagreed: curl used the second value and cut the body, Python and Chromium refused, Node fetch refused. Disagreement between parsers is what request smuggling attacks rely on (doc-based: RFC 9112 section 6.3 says a recipient must treat differing Content-Length values as an error).
- Content-Length with Transfer-Encoding. curl, Python and Chromium ignored Content-Length and used chunked, which matches RFC 9112 (doc-based: Transfer-Encoding overrides Content-Length, and a server must not send both). Node fetch rejected the response.
When to expect Content-Length versus chunked in the first place is covered in Transfer-Encoding chunked vs Content-Length.
3. Node http server with a wrong Content-Length
http.createServer((req, res) => {
res.setHeader('Content-Length', '20'); // body below is 11 bytes
res.end('hello world');
});
In Node 24.21.0 with default options, res.end() did not throw and nothing was logged. Node sent the header as given. With Content-Length 20, curl got 11 bytes and then hung until its timeout (curl: (28) ... with 11 out of 20 bytes received), because Node kept the keep-alive connection open. With Content-Length 5, curl received "hello" and exit code 0.
Doc-based, not tested here: Node's http.createServer({ strictContentLength: true }) option makes Node throw ERR_HTTP_CONTENT_LENGTH_MISMATCH instead of sending a mismatched body. The safest fix is to not set Content-Length by hand at all; res.end(body) sets a correct one for you when headers have not been sent yet. If you do set it, use Buffer.byteLength(body), not body.length, because non-ASCII characters take more than one byte in UTF-8.
4. Common causes
- Using string length instead of byte length (accents, emoji, CJK text).
- Changing the body after setting the header: compression, minification, injected scripts, or a template filled in later.
- Proxies or middleware that copy an upstream Content-Length after rewriting or decompressing the body.
- Adding a header in two layers (app and proxy), producing duplicate or conflicting lines.
5. How to check
curl -sv -o body.bin https://example.com/path
wc -c body.bin # compare with the Content-Length line in the -v output
curl's verbose output shows each Content-Length line it received, and the error codes above (18 and 8) point straight at the problem. You can also see the headers of any URL with the free HTTP headers checker.
Frequently asked questions
What does curl: (18) mean?
In curl 8.14.1 we got 'curl: (18) end of response with 9 bytes missing' when the server promised Content-Length: 20, sent 11 bytes and closed the connection. The body is shorter than the header says.
What causes net::ERR_CONTENT_LENGTH_MISMATCH in Chrome?
In Chromium 153, a navigation to a response with Content-Length: 20 and only 11 bytes before the server closed the connection failed with net::ERR_CONTENT_LENGTH_MISMATCH. fetch() in the same case rejected with TypeError: Failed to fetch.
What happens if Content-Length is smaller than the body?
Every client we tested (curl, Node fetch, Python requests, Chromium) returned only the first N bytes with no error. The extra bytes were silently dropped.
Why does my request hang when Content-Length is too large?
If the server keeps the connection open, the client waits for the missing bytes. curl, Node fetch, Python requests and Chromium all waited until a timeout in our test.
Is it OK to send Content-Length twice?
Two identical values worked in curl, Python requests and Chromium 153, but Node 24 fetch rejected it with UND_ERR_RES_CONTENT_LENGTH_MISMATCH. Two different values were rejected by Python, Chromium and Node fetch, and curl cut the body to the second value. Send it once.
Written with AI assistance. Measured on 2026-10-01 against a raw Node.js 24.21.0 net server on 127.0.0.1 sending hand-written HTTP/1.1 responses with an 11-byte body (hello world). Clients: curl 8.14.1, Node 24.21.0 fetch (undici), Python requests 2.32.3, and Chromium 153.0.8010.12 (Playwright, full chromium channel) for both fetch() and page navigation. Also a Node http server with a wrong Content-Length set by hand. PlainHeaders is our extension; the other methods are free.