Compression
An HTTP content coding that shrinks a message body, almost always a response body, so the same content crosses the network in fewer bytes and the recipient decodes it back exactly. gzip, Brotli and zstd are the common codings. Text gains a lot; already-compressed media gains little.
Also known as HTTP compression, Content compression.
Full Explanation
In HTTP, compression is a content coding. A content coding is an encoding transformation applied to a message body, almost always a response body. It lets the same content cross the network in fewer bytes. RFC 9110 defines content codings as a way to compress a representation "without losing the identity of its underlying media type and without loss of information" (RFC 9110, section 8.4.1). This means the transformation is lossless: the recipient decodes it and recovers the original bytes. The Content-Encoding header names whichever codings were applied. It lists them in the order they were applied (RFC 9110, section 8.4).
Compression is not minification. Minification rewrites the source text itself. It changes the bytes the client ends up with. Compression is not transfer coding either. RFC 9110 says the codings in Content-Encoding "are a characteristic of the representation". So they survive every hop between the sender that applied them and the client that decodes them. Transfer-Encoding works differently. It is negotiated between two adjacent nodes. It is rarely used for compression at all. Compression is also not the compression already baked into a media type. JPEG, WebP, MP4 and ZIP are compressed formats. Running gzip or Brotli over them "yields little to no improvement" (web.dev). Only three codings matter for web delivery today. The whole mechanism is four moves. First, the client advertises what it accepts in Accept-Encoding. Then the server or CDN picks one. It names the choice in Content-Encoding. Every cache in between keys on that choice with Vary: Accept-Encoding. Everything below is detail on those four moves.
How it works
The coding is chosen by proactive content negotiation. The client lists acceptable codings, optionally with quality weights, in the Accept-Encoding request header (RFC 9110, section 12.5.3). A server then tests acceptability by three rules. If there is no Accept-Encoding header at all, any coding is acceptable. A listed coding is acceptable unless it carries a qvalue of 0. And "when selecting between multiple content codings that have the same purpose, the acceptable content coding with the highest non-zero qvalue is preferred". If nothing on offer is acceptable, the origin server SHOULD send the response with no coding at all. The token identity is a synonym for "no encoding" in Accept-Encoding only. It is reserved and SHOULD NOT appear in Content-Encoding.
Coding names are case-insensitive. They "ought to be registered within the "HTTP Content Coding Registry"" (RFC 9110, section 8.4.1). IANA publishes that registry as part of the HTTP Parameters registry. The body then stays encoded in transit and in cache: "typically, the representation is only decoded just prior to rendering or analogous usage".
- gzip: the gzip file format (RFC 1952). It wraps a deflate stream and a 32-bit CRC. RFC 9110 describes the coding as "an LZ77 coding with a 32-bit Cyclic Redundancy Check (CRC)" (section 8.4.1.3). Note that deflate is a different registered coding. It is the same LZ77-plus-Huffman stream in a zlib wrapper (section 8.4.1.2). It is little used on the web because some implementations omit that wrapper.
- Brotli (br): a combination of "the LZ77 algorithm and Huffman coding" plus a built-in static dictionary of common web strings (RFC 7932, section 8 and appendix A). It is designed to compress "considerably better than the gzip program" (RFC 7932, section 1.1). All modern browsers advertise gzip and Brotli in
Accept-Encodingby default (web.dev). - Zstandard (zstd): a newer lossless format (RFC 8878). Browsers cap decoder window sizes to bound memory, and that caused decode failures. So RFC 9659 turned the window limit into a requirement for the HTTP coding: "decoders MUST support a Window_Size of up to and including 8 MB, and encoders MUST NOT generate frames requiring a Window_Size larger than 8 MB" (RFC 9659, section 3).
- dcb and dcz: dictionary-compressed Brotli and Zstandard, added by Compression Dictionary Transport (RFC 9842, September 2025). A client that already holds a matching dictionary sends its SHA-256 hash in Available-Dictionary. The server can then encode only the delta. This is how app.v2.js can be sent as a diff against app.v1.js.
Compression level trades ratio against CPU. "For gzip, compression settings range from 1 to 9, with 9 being the best. For Brotli, this range is 0 to 11, with 11 being the best. However, higher compression settings require more time." For content compressed at request time (dynamic compression), "settings in the middle of the range tend to offer the best trade-off between compression ratio and speed". For content compressed ahead of time (static compression), you can "use the most aggressive compression settings available" because the cost is paid once (web.dev).
Because the chosen coding differs per client, a compressed response must carry Vary: Accept-Encoding. "Vary expands the cache key required to match a new request to the stored cache entry" (RFC 9110, section 12.5.5). A cache "MUST NOT use that stored response without revalidation unless all the presented request header fields nominated by that Vary field value match those fields in the original request" (RFC 9111, section 4.1). So the gzip copy and the Brotli copy of one URL are separate entries under one cache key prefix. The Vary header is what makes that safe. The same section permits a cache to normalize a nominated header "in a way that is known to have identical semantics". This is the standards basis for CDNs collapsing the many equivalent spellings of Accept-Encoding into a few variants.
Why it matters for a CDN
A CDN's bill and its user-visible speed are both dominated by bytes moved. Compression is the cheapest way to move fewer. gzip and Brotli "perform best on text-based content, often achieving compression rates of as high as 70-90% for larger files" (web.dev). HTML, CSS, JavaScript, JSON and SVG are exactly what an edge serves most often. Every byte not sent is bandwidth and egress cost not paid. On a slow last mile, it is latency not paid either.
Placing the work at the edge changes the economics twice over. Compressing a cacheable object once at the edge and serving the result to thousands of clients spares the origin the CPU entirely. That is the difference between paying for compression per request and paying for it per cache fill. A CDN sitting in the middle can also change the coding. "Cloudflare can receive content from your origin server with Brotli or Gzip compression and serve it to visitors uncompressed (or vice versa), independently of caching" (Cloudflare). So an origin that only speaks gzip can still deliver Brotli to browsers.
The cost side is cache footprint. Every coding you negotiate is another stored variant of the same URL. So it consumes more cache, and each variant warms independently. That lowers hit ratio per variant. That is why every CDN in the next section normalizes Accept-Encoding, restricts compression to an allow-list of content types, and imposes a minimum body size.
What CDNs do
All four deliver compressed text at the edge. But what they compress, when they compress it, and whether it is on by default differ sharply.
- Cloudflare: "supports Gzip, Brotli, and Zstandard compression when delivering content to website visitors". The default depends on the plan: Zstandard on Free, Brotli on Pro and Business, Gzip on Enterprise. It fetches from the origin with a fixed
accept-encoding: br, gzipon all plans. It compresses only an allow-listed set of content types. It compresses only status 200 for success and only 403 or 404 for errors, and only bodies at or above 48 bytes for Gzip and 50 bytes for Brotli and Zstandard. Defaults are overridden with Compression Rules. An origin that sends cache-control: no-transform stops Cloudflare altering compression and keeps its own Content-Length. Cloudflare notes that the no-transform header has to come from the origin. A client cannot add it. - CloudFront: opt-in. Set Compress objects automatically to Yes and enable both Gzip and Brotli in a cache policy. Or set Compress, EnableAcceptEncodingGzip and EnableAcceptEncodingBrotli via the API. "When the viewer supports both Gzip and Brotli, CloudFront uses Brotli." It compresses objects "between 1,000 bytes and 10,000,000 bytes in size", only the listed content types, and "only when the HTTP status code of the response is 200, 403, or 404". The origin must send Content-Length, or nothing is compressed. If the origin already sent
Content-Encoding, CloudFront caches and forwards that body untouched. And when it compresses an object with a strong ETag, it rewrites it to a weak ETag so the compressed and uncompressed forms stay comparable. Compression is best-effort: CloudFront may skip it under high load and then cache the uncompressed object. - Fastly: Gzip and Brotli, in two distinct modes. Static compression is VCL-only and runs pre-cache "when responses are received from origin". It is enabled from the web interface, the API, or the beresp.gzip and beresp.brotli variables in vcl_fetch. So the compressed object is what gets cached. Dynamic compression runs post-cache per response. It works on Compute as well as VCL. It is switched on by setting the X-Compress-Hint header in vcl_deliver. Fastly also rewrites inbound
Accept-Encodingto a single token: br, gzip, deflate, or removed entirely. It keeps the original in Fastly-Orig-Accept-Encoding, so that semantically equivalent requests share one cached variant. Note that a CDN service cannot decompress a compressed origin response. So the origin must still be able to answer uncompressed. - Akamai: two separate behaviours. Brotli Support serves and caches Brotli that your origin produced: it "doesn't compress resources within the Akamai CDN in real-time. You need to set up Brotli compression separately on your origin server". It accepts only compression level 6. Last Mile Acceleration is the on-the-fly gzip path. "860 bytes is the smallest object that Akamai edge servers will gzip compress". Akamai recommends applying it to listed text types "greater than 4.2 KB in size", never to PDFs ("if LMA tries to compress a PDF, an error is thrown"), images, archives or streaming media. Edge compression beyond that is a product feature rather than an absence: on Ion, Adaptive Acceleration "offers several forms of compression, including Brotli".
Watch out for
- Already-compressed bodies cost CPU and can grow. MDN is blunt about it: "it usually provides nothing to compress them a second time. In fact, this is often counterproductive as the cost of the overhead (algorithms usually need a dictionary that adds to the initial size) can be higher than the extra gain in compression resulting in a larger file" (MDN). Fastly says the same: "only compress formats that are not already compressed". Exclude images, audio, video and archives.
- The first variant cached can win for the whole TTL. On CloudFront, "if a viewer requests a specific cached object that uses Gzip compression, and the viewer accepts the Gzip format, subsequent requests to the same object will always return the Gzip version, even if the viewer accepts both Brotli and Gzip". Akamai behaves the same way. If a gzip copy is already cached, "the gzip resource is served to save time and bandwidth". You must wait for the TTL to expire. Turning compression on does not retroactively compress what is already in cache. You must purge it instead.
- Missing
Vary: Accept-Encoding. A cache that was never told the response was negotiated can hand a compressed body to a client that cannot decode it. RFC 9842 states the failure directly for the dictionary codings: a cacheable response "MUST include a "Vary" header to prevent caches from serving dictionary-compressed resources to clients that don't support them or serving the response compressed with the wrong dictionary" (RFC 9842, section 6.2). Emit it on every response where compression was even considered, compressed or not. - Compressing secrets with attacker-controlled input. The TIME and BREACH attacks "make similar use of HTTP-level compression to decrypt secret data passed in the HTTP response". There is no TLS-level fix: "application-level mitigations are needed" (RFC 7457, section 2.6). Compressing a dynamic HTML page that contains both a CSRF token and reflected user input is the classic case. Randomize the token, or exclude the response.
- Tiny bodies. Framing overhead can exceed the saving. So vendors set floors: Cloudflare 48 bytes for Gzip and 50 for Brotli and Zstandard, CloudFront 1,000 bytes, Akamai 860 bytes for LMA. A response below the floor is simply sent uncompressed.
- Content types and status codes are allow-lists. Both Cloudflare and CloudFront compress only listed Content-Type values and only status 200, 403 and 404. So a 500 error page or an unlisted media type ships uncompressed no matter how compressible it is. Check the list before assuming your API's content type is on it.
- Brotli over plain HTTP. "Chrome and Firefox web browsers support Brotli compression only when the request is sent using HTTPS. They don't support Brotli with HTTP requests" (CloudFront). So cleartext traffic falls back to gzip.
- zstd is not a drop-in default. Browsers cap zstd window sizes, "thereby causing interoperability issues" (RFC 9659). That is why the 8 MB requirement exists. On the CDN side, Cloudflare documents Zstandard both as the Free-plan default and as opt-in: "customers can enable Zstandard compression through Compression Rules". The CloudFront, Fastly and Akamai pages cited above document only Gzip and Brotli. So treat zstd as per-platform rather than assumed.
- Streaming manifests are text, which is the trap. HLS and DASH manifests and caption files fall into the text/ content types. Akamai warns that "compression of these files can result in poor performance or break stream delivery". It recommends you keep LMA out of Adaptive Media Delivery properties. CloudFront, by contrast, lists application/vnd.apple.mpegurl and application/dash+xml among the types it will compress. Media segments themselves are already compressed and gain nothing. Decide this per platform and test the player. Do not assume either default.
Best practice
- Compress text and leave everything else alone. Compress HTML, CSS, JavaScript, JSON, XML, SVG, plain text, fonts and wasm. Leave out images, audio, video and archives.
- Offer Brotli first and gzip as the fallback: Brotli is designed to beat gzip on ratio, and every modern browser advertises both. Add zstd deliberately, per platform, not as an assumption.
- Pre-compress cacheable assets at the most aggressive level in the build. Use a mid-range level for anything compressed per request. Paying level 11 once per deploy is not the same trade as paying it once per request.
- Emit
Vary: Accept-Encodingon every response where compression was considered, whether or not that response was compressed. Let the CDN normalizeAccept-Encodingso the variant count stays small. - Let the edge do the compressing for cacheable content, so the origin pays once per fill instead of once per request. Check your vendor's minimum size, content-type allow-list and status-code restriction rather than assuming a response was compressed.
- Exclude any response that mixes a secret with reflected input. Exclude streaming manifests too, unless you have tested the players end to end.
- Verify in production, not in configuration: request the URL with and without
Accept-Encodingand compareContent-Encoding,Varyand the transferred size.
Interactive Animation
Examples
# Enable compression in Nginx
gzip on;
gzip_types text/html text/css application/javascript application/json;
gzip_min_length 256;
gzip_vary on; # Adds Vary: Accept-Encoding
# Test compression with curl
$ curl -H 'Accept-Encoding: gzip, br' -sI https://example.com/ | grep -i 'content-encoding\|content-length'
Content-Encoding: br
Content-Length: 4231
# Compare uncompressed vs compressed
$ curl -sI https://example.com/ | grep content-length
Content-Length: 18920 # ~78% reduction with Brotli
Frequently Asked Questions
An HTTP content coding that shrinks a message body, almost always a response body, so the same content crosses the network in fewer bytes and the recipient decodes it back exactly. gzip, Brotli and zstd are the common codings. Text gains a lot; already-compressed media gains little.
# Enable compression in Nginx
gzip on;
gzip_types text/html text/css application/javascript application/json;
gzip_min_length 256;
gzip_vary on; # Adds Vary: Accept-Encoding
# Test compression with curl
$ curl -H 'Accept-Encoding: gzip, br' -sI https://example.com/ | grep -i 'content-encoding\|content-length'
Content-Encoding: br
Content-Length: 4231
# Compare uncompressed vs compressed
$ curl -sI https://example.com/ | grep content-length
Content-Length: 18920 # ~78% reduction with Brotli
Yes. Compression is also known as HTTP compression, Content compression. An HTTP content coding that shrinks a message body, almost always a response body, so the same content crosses the network in fewer bytes and the recipient decodes it back exactly. gzip, Brotli and zstd are the common codings. Text gains a lot; already-compressed media gains little.
Related CDN concepts include:
- Content-Encoding — The HTTP header naming the content codings applied to a body (gzip, br, zstd), which …
- Vary Header — Vary is the response header that names the request headers the origin used to select …
- Brotli — Brotli is a lossless compression format from Google, specified in IETF RFC 7932 and carried …