Transfer-Encoding transfer both
Specifies how the message body is encoded for transfer — the most important value being chunked, which allows streaming responses without a known Content-Length.
What it does
Transfer-Encoding specifies how the message body is encoded for the purpose of safe transport between nodes. Unlike Content-Encoding (which is an end-to-end property of the content), Transfer-Encoding is a hop-by-hop header — it applies only to the current connection and must be stripped before forwarding.
The most important and only widely-used value is chunked, which allows a server to start sending a response before it knows the total size — essential for streaming, server-sent events, and large dynamically-generated responses.
Syntax
Transfer-Encoding: chunked
Transfer-Encoding: gzip, chunked
Transfer-Encoding: compress
Transfer-Encoding: deflate
Transfer-Encoding: identity
Multiple encodings can be listed (applied in order). chunked must always be the last encoding applied.
Chunked transfer encoding
chunked is by far the most common Transfer-Encoding value. It splits the body into a series of chunks, each prefixed with its size in hexadecimal. A zero-length chunk signals the end:
HTTP/1.1 200 OK
Content-Type: text/html
Transfer-Encoding: chunked
4\r\n
Wiki\r\n
6\r\n
pedia \r\n
E\r\n
in\r\n\r\nchunks.\r\n
0\r\n
\r\n
Each chunk:
- Size in hex (
4= 4 bytes,E= 14 bytes) \r\n- The chunk data
\r\n
Terminal chunk: 0\r\n\r\n
The receiving end reassembles the chunks into the complete body. The HTTP client/framework does this automatically — application code never sees the chunked framing.
Why chunked matters
Without chunked encoding, a server must know the full response size before sending anything. This means buffering the entire response body in memory, then setting Content-Length, then sending. For large responses or dynamically generated content, this is expensive or impossible.
With Transfer-Encoding: chunked, the server can start sending immediately as data becomes available. No buffering, no upfront size calculation. The response streams to the client in real time.
Use cases:
- Streaming large file downloads
- Server-Sent Events (SSE)
- Long-running queries returning results incrementally
- Proxied responses where the upstream size is unknown
- AI/LLM streaming token responses
Transfer-Encoding vs Content-Encoding
These two are frequently confused:
Transfer-Encoding |
Content-Encoding |
|
|---|---|---|
| Scope | Hop-by-hop (current connection only) | End-to-end (origin to client) |
| Stripped by proxies | Yes | No |
| Purpose | Safe transport framing | Content compression/transformation |
| Affects Content-Length | No (chunked replaces it) | Yes (reflects compressed size) |
| HTTP/2 support | No (chunked forbidden) |
Yes |
A response can have both: Transfer-Encoding: gzip, chunked means the body is gzip-compressed AND chunked for transport. The proxy strips the chunked encoding and de-chunks before forwarding, but leaves the gzip compression intact.
Forbidden in HTTP/2
Transfer-Encoding: chunked is forbidden in HTTP/2. HTTP/2 has its own binary framing with built-in stream multiplexing — it doesn't need HTTP/1.1's chunked mechanism. HTTP/2 DATA frames handle the same streaming use case natively. A server must not send Transfer-Encoding in HTTP/2 responses, and an HTTP/2 client that receives it should treat it as a protocol error.
When a reverse proxy translates between HTTP/2 (origin-facing) and HTTP/1.1 (client-facing), it handles this conversion transparently.
Trailers with chunked
Transfer-Encoding: chunked enables HTTP trailers — headers sent after the body, in the terminal chunk section. Declared in the Trailer header:
HTTP/1.1 200 OK
Transfer-Encoding: chunked
Trailer: Expires
4\r\n
data\r\n
0\r\n
Expires: Wed, 25 Jun 2026 12:00:00 GMT\r\n
\r\n
Useful for checksums and metadata that can only be computed after the body is fully sent.
Common mistakes and gotchas
Setting both Content-Length and Transfer-Encoding: chunked. They're mutually exclusive framing mechanisms. If both appear, Transfer-Encoding takes precedence and Content-Length must be ignored. Some intermediaries may remove one or both — behaviour varies.
Using chunked in HTTP/2. Protocol error. HTTP/2 clients and servers must not use Transfer-Encoding: chunked. Streaming in HTTP/2 is handled at the framing layer.
Not stripping Transfer-Encoding when proxying. A proxy that forwards Transfer-Encoding: chunked downstream without de-chunking first may confuse the next recipient (especially if forwarding over HTTP/2). Proxies must handle de-chunking before forwarding.
Real-world examples
Streaming API response:
HTTP/1.1 200 OK
Content-Type: application/json
Transfer-Encoding: chunked
1a\r\n
{"id":1,"status":"processing"}\r\n
1b\r\n
{"id":2,"status":"processing"}\r\n
0\r\n
\r\n
nginx config to enable chunked proxying:
location /stream {
proxy_pass http://backend;
proxy_buffering off;
# Transfer-Encoding: chunked is set automatically
}
FAQ
Does my app need to implement chunked encoding manually?
No. Web frameworks (Laravel, Express, Django, Rails) and web servers (nginx, Apache, Caddy) handle chunked encoding transparently. You write your response normally; the server handles the chunked framing. You only interact with chunking directly if you're building a low-level HTTP server or client from scratch.
What's the hex size in chunked encoding — is it bytes?
Yes — the hex number before each chunk is the size of that chunk in bytes. a = 10 bytes, 1f = 31 bytes, 0 = end of body.
Can chunked encoding be used on requests (not just responses)?
Yes — HTTP/1.1 allows chunked requests too. Some clients use it for large file uploads when the total size isn't known upfront. However, server support for chunked request bodies is less universal than for responses, and many servers require Content-Length on requests.
Fun fact
Chunked transfer encoding was one of HTTP/1.1's most impactful additions, solving a problem that HTTP/1.0 had no answer for: how do you stream a response of unknown size over a persistent connection? HTTP/1.0 solved it by closing the connection to signal end-of-body — which worked but killed connection reuse. Chunked encoding gave HTTP/1.1 both streaming and persistent connections simultaneously. Today, HTTP/2's binary framing is more elegant, but chunked encoding served the web faithfully for nearly 20 years and still does for any HTTP/1.1 traffic.