Pricing - Pricing & Costs
BunnyCDN Alternatives: Cheaper CDN Options Compared
BunnyCDN Alternatives: Cheaper CDN Options Compared Most teams searching for a BunnyCDN alternative are not chasing a ...
Chunked transfer encoding is the HTTP/1.1 framing that lets a server start writing a response body before it knows the total length: each piece is prefixed with its size in hexadecimal, and a zero-length chunk terminates the message. The payoff shows up in time to first byte. A report that needs 2.5 seconds to assemble can put its headers and first rows on the wire in under 150 ms, while the framing tax runs about 5 to 12 bytes per chunk. That is below 0.2% overhead at 8 KB chunks and above 10% at 64-byte chunks, which is why chunk sizing matters more than most teams assume.

The response carries a Transfer-Encoding header with the value chunked and no Content-Length. The two are mutually exclusive; a message with both is malformed and a compliant proxy must reject or fix it. The body then becomes a sequence of chunks, each one a hexadecimal size line ended by CRLF, the raw bytes, and another CRLF.
The message ends with a chunk of size zero, followed by optional trailer fields and a final CRLF. Those five terminating bytes are the only signal the client has that the response is complete rather than truncated. A connection that dies before the terminal chunk arrives is a detectable error, which is the one real advantage chunked encoding has over the HTTP/1.0 pattern of delimiting the body by closing the connection.
Trailers are the underused part of the spec. They let a server send integrity or status metadata computed only after the body is generated, which is how gRPC over HTTP/1.1 delivers its status code. Browser fetch exposes them inconsistently, so treat trailers as viable between your own services and unreliable toward end users.
Chunked transfer encoding is an HTTP/1.1-only mechanism. RFC 9113 forbids the Transfer-Encoding header in HTTP/2, and HTTP/3 per RFC 9114 does the same, because both protocols frame bodies natively in DATA frames and no longer need a length up front. An HTTP streaming response over HTTP/2 is just a stream that stays open, and the concept survives only as a translation problem at proxies that terminate HTTP/2 in front and speak HTTP/1.1 to origin.
That translation is where most production surprises come from. The edge receives DATA frames, the origin connection speaks chunked, and each layer in between has an independent opinion about how much to accumulate before forwarding.
An HTTP streaming response is only as responsive as its most eager buffer. Four layers commonly hold bytes, and each one has a different default.
| Layer | Typical default | What to change |
|---|---|---|
| Application runtime | Response accumulated, length computed, one write | Use the streaming response primitive and flush per logical unit |
| Reverse proxy (nginx, HAProxy, Envoy) | nginx buffers upstream into 4 to 8 KB buffers before forwarding | Disable response buffering on streaming paths only |
| Compression filter | Deflate window fills before emitting output | Disable compression for event streams, or flush per record |
| CDN edge | Pass-through for most misses, full buffering on some cache-write paths | Mark streaming paths uncacheable and verify pass-through |
If any single layer in that chain waits for a full buffer, the reader sees the latency of the slowest buffer, not the latency of your first flush.
On nginx, the streaming path needs four directives inside the relevant location: proxy_buffering off, proxy_http_version 1.1, proxy_set_header Connection "" to keep the upstream connection reusable, and gzip off for text event streams. Add proxy_read_timeout 3600s for long-lived streams, and keep chunked_transfer_encoding on so nginx re-frames toward HTTP/1.1 clients. Apply this per location, never globally: turning off response buffering site-wide ties a worker connection to slow clients for the duration of every large object transfer.
Request buffering deserves separate thought. A client uploading with chunked transfer encoding will be fully accumulated by default before the proxy contacts origin, which is correct for a slow-origin upload endpoint and wrong for a real-time ingest endpoint.
Most modern edges pass chunks through on a cache miss and write to cache concurrently, so the first client is not penalized for filling the cache. Two behaviors still bite. First, request collapsing: when several clients ask for the same uncached streaming URL, some configurations queue followers behind the leader, which is disastrous for a per-user event stream and helpful for a shared live segment. Second, some edges will only serve a cached copy once the object is complete, meaning a partial stream is refetched rather than shared.
The practical rule is to keep streaming endpoints on a path prefix that is explicitly non-cacheable and exempt from collapsing, and keep cacheable large objects on a path where buffering and Content-Length rewriting are welcome. Whether that separation is expressible per path or only per account is worth asking during vendor evaluation; BlazingCDN, for example, treats per-configuration delivery rules for streaming and large-file paths as part of its feature set rather than a support ticket.
Without Content-Length, clients lose progress percentage and byte-range requests become impossible for that response. That is fine for a server-sent events feed and unacceptable for a 4 GB installer, where a lost connection at 92% means starting over. Never generate large downloadable artifacts with chunked encoding if you can compute the size.
The harder failure mode is error handling after commit. Once you flush the first chunk, status 200 is already on the wire; a database timeout at row 40,000 leaves you with a syntactically valid but semantically truncated response. The mitigations are trailers between internal services, an explicit end-of-stream sentinel record for browser clients, or both. Silent truncation that looks like success is the single most common bug in production chunked APIs.
Idle timeouts are next. Intermediaries commonly close connections after 60 seconds of inactivity, so long-lived streams need a comment or heartbeat frame every 15 to 30 seconds. Finally, tiny chunks interact badly with Nagle and with TLS record overhead: a 20-byte payload can cost 5 bytes of chunk framing plus 20 to 40 bytes of TLS record and header overhead, so coalescing to at least a few hundred bytes per flush is usually the right trade against latency.
Measure the gap between response-header arrival and first body byte, not just time to first byte; browser Resource Timing and most HTTP clients expose both. If that gap tracks your total generation time, something is buffering. Confirm the Transfer-Encoding header with value chunked is present on HTTP/1.1 responses and absent on HTTP/2 responses, since its presence over HTTP/2 means a broken intermediary. Then compare edge access log upstream-response-time against your application's own first-flush timestamp; a difference above roughly 200 ms points at the proxy or compression layer, not the origin.
No. HTTP/2 and HTTP/3 frame message bodies natively and forbid the Transfer-Encoding header, so chunked framing is unnecessary and non-compliant there. It remains essential on HTTP/1.1 hops, which still exist between most proxies and origins. Streaming semantics carry over unchanged; only the wire framing differs.
Yes, most edges write to cache while streaming to the first client and then serve subsequent requests with a normal Content-Length. The caveats are request collapsing, which can queue followers behind the first request, and configurations that only serve fully written objects. Per-user streams should be marked non-cacheable explicitly.
Almost always upstream response buffering or gzip. nginx accumulates the origin response into 4 to 8 KB buffers by default, and the compression filter holds output until its window fills. Disabling both on that location, with HTTP/1.1 toward upstream and a raised read timeout, restores per-event delivery.
Flush per logical record, but coalesce so chunks are at least a few hundred bytes where latency allows. Chunk framing costs about 5 to 12 bytes, so 64-byte chunks waste over 10% of bytes once TLS record overhead is added, while 8 KB chunks cost under 0.2% and delay interactive output noticeably.
Pick one streaming endpoint and add a log field for the timestamp of your first body write. Compare it to the edge log's upstream response time and to a browser's response-start metric for the same request. If the three numbers spread by more than 200 ms, walk the chain in order: application flush, proxy response buffering, compression filter, edge cache-write path. Then decide deliberately which paths keep Content-Length for range support and which use chunked transfer encoding for latency. That decision is architectural, and it is usually made by accident.
Pricing - Pricing & Costs
BunnyCDN Alternatives: Cheaper CDN Options Compared Most teams searching for a BunnyCDN alternative are not chasing a ...
Pricing - Pricing & Costs
Akamai CDN Pricing in 2026: What It Really Costs The single most useful number in any Akamai pricing conversation is ...