Transfer-Encoding: chunked vs Content-Length: when servers use which (tested in Node and nginx)

An HTTP/1.1 body needs a known end: either a Content-Length up front, or chunked encoding where each piece carries its own size. We logged which one Node and nginx picked.

1. Which header did the response get?

Server / caseResponse framing
Node res.end(body) in one call (12 B and 5,000 B)Content-Length
Node res.write() then res.end()Transfer-Encoding: chunked
nginx static fileContent-Length: 17
nginx static file, gzip on, client sends Accept-Encoding: gzipchunked + Content-Encoding: gzip (compressed size isn't known in advance)
nginx proxy_passpassed through: upstream Content-Length stayed Content-Length, upstream chunked stayed chunked
nginx, client speaking HTTP/1.0neither header: body ends when the connection closes

So "why does my response have no Content-Length?" usually means compression or streaming. Both are fine; clients handle either.

2. What chunks look like

$ curl --raw http://localhost/write    # CR/LF shown as \r\n
6\r\n
part1\n\r\n
6\r\n
part2\n\r\n
0\r\n
\r\n

Each chunk is its size in hex, CRLF, the data, CRLF; a zero-size chunk ends the body. Plain curl (without --raw) strips this and shows only part1/part2. HTTP/2 and HTTP/3 have no chunked encoding at all; framing is built into the protocol.

3. Requests, and never sending both

Check what a live URL sends with the free HTTP headers checker; for compression choices see gzip vs br in nginx, measured.

Frequently asked questions

What is the difference between Transfer-Encoding chunked and Content-Length?

Content-Length gives the body size up front; chunked sends the body in pieces, each prefixed with its size, ending with a 0-size chunk. In our tests Node used Content-Length for res.end(body) and chunked for res.write() streaming.

Why does nginx send Transfer-Encoding: chunked instead of Content-Length?

In our test a static file had Content-Length until gzip compressed it; the gzip response was chunked because the compressed size isn't known in advance. Proxied responses kept the upstream's framing.

Can a request have both Content-Length and Transfer-Encoding?

It shouldn't: nginx 1.31 and Node 24 both answered 400 Bad Request to one that did.

Does HTTP/2 use chunked encoding?

No. HTTP/2 and HTTP/3 frame data themselves; Transfer-Encoding: chunked is HTTP/1.1 only.

How do I see the raw chunks?

curl --raw URL shows the hex sizes and CRLFs; plain curl decodes them.

Written with AI assistance. Measured on 2026-10-01 with Node.js 24 (http module) and a throwaway nginx 1.31.6 container (static files with gzip on, and proxy_pass to the Node server; removed afterwards), using curl -D and --raw and raw requests over nc. The HTTP/2 remark is from RFC 9113, not this test. PlainHeaders is our extension; the other methods are free.