Brotli
Brotli is a lossless compression format from Google, specified in IETF RFC 7932 and carried on the web as the "br" HTTP content coding. Google measured a 13-21% higher compression ratio than deflate at equal quality, paid for in encoder time, not decode speed. Every current major browser accepts it.
Also known as Brotli Compressed Data Format.
Full Explanation
Brotli is a lossless, general-purpose compression format designed at Google. IETF RFC 7932 specifies it (July 2016, Informational). On the web it travels under the token br. This is the HTTP content coding that RFC 7932 registered with IANA. Browsers advertise it in Accept-Encoding, and servers name it in Content-Encoding. The specification states its own goal plainly: a compression ratio "considerably better than the gzip program" while decompressing "much faster than current LZMA implementations" (RFC 7932 section 1.1). It is not a universal codec, and it is deliberately incomplete: the format offers no random access, and it carries no checksum and no uncompressed length. It does not try to compress specialised data, such as raster graphics, as densely as algorithms built for it. The same format is an integral part of the WOFF 2.0 web-font file format (RFC 7932 section 1.2).
How it works
A Brotli stream is a header followed by a series of meta-blocks (RFC 7932 section 2). The header carries the sliding-window size. This is a power of two minus 16 bytes. The exponent ranges from 10 to 24, so the window ranges from 1 KiB - 16 B up to 16 MiB - 16 B. Gzip, by contrast, has a fixed 32 KB window. Be careful how much weight you put on that. Cloudflare measured it. Cloudflare adds that the difference "is almost irrelevant in web-server context, as text files larger than 32KB are the minority" (Cloudflare, 2016).
Each meta-block is compressed with a combination of the LZ77 algorithm and Huffman coding. The prefix codes are chosen per meta-block. They are independent of those in neighbouring meta-blocks. The matches are not independent: a back-reference may point into a previous meta-block, as far back as the window size. The compressed data is a series of commands. Each command is a run of literal bytes followed by a pointer to a duplicated string, written as a pair <length, backward distance>. The minimum match is two bytes.
Three things then separate Brotli from deflate. First, a static dictionary. A decoded distance can exceed the largest legal backward distance. When it does, it is read instead as a reference into a dictionary. That dictionary ships with every implementation. It holds 13,504 words and syllables of English, Spanish, Chinese, Hindi, Russian and Arabic. It also holds common phrases from machine-readable languages, particularly HTML and JavaScript. It totals 122,784 bytes (Alakuijala et al., Google, 2015). Each base word has 121 transformed forms: prefixes, suffixes, case changes and omissions. A dictionary copy length must be between 4 and 24 bytes (RFC 7932 section 8). Second, second-order context modeling. The prefix code used for a literal or a distance depends on the current block type. It also depends on a context ID computed from the previous two bytes. So a byte following "<" is coded by a different tree than a byte following ";" (RFC 7932 section 7). Third, entropy codes are reused across the stream, and distances are coded jointly with lengths. Google credits the density gain to exactly that set. It names "a 2nd order context modeling, re-use of entropy codes, larger memory window of past data and joint distribution codes" (Google Open Source Blog).
An encoder exposes a quality parameter from 0 to 11, defaulting to 11. It also exposes a window exponent, defaulting to 22 (reference library encode.h). Quality buys density with encoder time and almost nothing else. On Google's three test corpora, brotli at quality 9 reached a compression ratio 13-21% higher than deflate at level 9. That works out to 12-17% fewer output bytes, while encoding 32.3% slower. Decompression stayed slightly faster than deflate's, with a 5.7% advantage on the geometric mean (Google comparison study). Note the shape of that figure. It is a ratio increase, not a byte saving, and the two are not interchangeable. A 21% higher ratio removes about 17% of the bytes, not 21%.
Why it matters for a CDN
A CDN ships the same HTML, CSS, JavaScript, JSON and SVG out of many points of presence. So a byte saved is saved on every request: less egress billed, less time on the wire. The gains concentrate exactly where web traffic lives, in small text files. Over 10,655 HTML, CSS and JavaScript files, Cloudflare found Brotli at maximum quality produced "1.19X smaller results than zlib at the maximal quality". Files under 1 KB were 1.38x smaller. In their words, that improvement "can probably be attributed to the use of static dictionary". Amazon states that CloudFront's Brotli edge compression "delivers up to 24% smaller file sizes as compared to Gzip" (AWS, September 2020).
The bill arrives as encoder CPU. It lands in a place a CDN has to plan around. In that same Cloudflare run, brotli 9 compressed at 8.8 MB/s against zlib 9's 41.9 MB/s. Brotli 10 collapsed to 0.5 MB/s. So practice splits in two. Cacheable static assets are compressed once, ahead of time, at the highest quality the build can afford. Uncacheable dynamic responses are compressed per request at a modest quality, commonly 4 to 6. Read Cloudflare's finding properly before treating any level as a rule, though. Brotli 4 was, averaged over all files, slightly faster than zlib 8 at a comparable ratio. But they immediately warn "that is misleading". On the sub-64 KB files that dominate real traffic, brotli 4 was 1.48x slower than zlib 8. Their own conclusion was that quality 5 pays off for files over 64 KB on slow connections. They shipped a minimum-size setting so that gzip handled the small ones. The numbers are also from 2016 hardware and a 2016 encoder. Benchmark your own mix.
The second CDN-specific consequence is cache identity. A compressed body is one of several negotiated representations of the same URL. So the origin should send Vary: Accept-Encoding. This header "expands the cache key required to match a new request to the stored cache entry" (RFC 9110 section 12.5.5). The CDN's cache key must honour it. Omit it, and a cache may hand a Brotli body to a client that asked only for gzip. That client cannot decode it. The opposite failure is variant explosion, because clients send dozens of equivalent Accept-Encoding spellings. RFC 9111 section 4.1 permits a cache to normalise a field value "in a way that is known to have identical semantics" before matching. CDNs use that licence to collapse the variants and protect hit ratio.
What CDNs do
- Cloudflare delivers gzip, Brotli or Zstandard to visitors, chosen per request from Accept-Encoding. The default depends on the zone plan: Zstandard on Free, Brotli on Pro and Business, gzip on Enterprise. Towards the origin it always asks with the header accept-encoding: br, gzip, on every plan (Cloudflare docs).
- Fastly compresses with gzip or Brotli in two distinct places. Static compression runs pre-cache as the response arrives from origin. It is available only in VCL CDN services. It is enabled through the web interface, the API, or the beresp.brotli variable in vcl_fetch. Dynamic compression runs post-cache on the way out and is triggered by setting the X-Compress-Hint header. Fastly also normalises Accept-Encoding automatically "to reduce the number of permutations" (Fastly docs).
- Amazon CloudFront has compressed at the edge with Brotli since September 2020. It is opt-in: set Compress objects automatically and attach a cache policy, because "Brotli doesn't support legacy cache settings". When a viewer accepts both codings, CloudFront picks Brotli. It compresses only objects between 1,000 and 10,000,000 bytes that arrive with a Content-Length and a status of 200, 403 or 404. It does so on a best-effort basis. Under high traffic load it skips compression, and caches the uncompressed object instead (CloudFront docs).
- Azure Front Door supports gzip and brotli, and "if the request supports more than one compression type, brotli compression takes precedence". Compression is part of Enable Caching on a route, so an uncached route cannot use it. A file qualifies only if it has a listed MIME type. It must also be larger than 1 KB and smaller than 8 MB (Azure docs).
- Shared dictionaries push Brotli past a single response. RFC 9842 (Standards Track, September 2025) registers the dcb coding. It is a 4-byte magic number plus a 32-byte SHA-256 hash of an external dictionary. This is followed by a Shared Brotli stream compressed against a resource the browser already holds. Only the delta travels. It is HTTPS-only by specification and today Chromium-only (Chrome and Edge 130 or later). Cloudflare offers it in beta as a passthrough mode. The origin creates the dictionaries and the delta responses. Cloudflare forwards the headers unmodified and varies the cache per dictionary (Cloudflare docs).
Watch out for
- Encoder cost at the edge. On-the-fly Brotli is far more expensive than gzip at comparable quality numbers. Slower compression can make a response arrive later, not sooner. As Cloudflare puts it, "slower compression will actually slow the connection down". Treat quality as a latency setting, not a size setting.
- No integrity check. The wire format carries no checksum and no uncompressed length. The reference implementation warns that you can modify raw ranges of a compressed stream. It says "the decoder will not notice that" (brotli README). Cloudflare notes Brotli "drops the error detection CRC check present in gzip". End-to-end integrity has to come from elsewhere in the stack.
- Missing Vary. A cache must not reuse a varied response "unless the later request has the same values for the listed header fields". With no Vary at all, there is nothing to constrain reuse. So a Brotli body can be served to a client that never asked for it (RFC 9110 section 12.5.5).
- HTTPS only in Chrome and Firefox. Both browsers "support Brotli compression only when the request is sent using HTTPS. They don't support Brotli with HTTP requests". So a plain-HTTP origin falls back to gzip for the bulk of the web (CloudFront docs).
- Already-compressed payloads. Images, audio, video and archives gain little or nothing. By design, the format does not compress specialised data, such as raster graphics, as densely as dedicated codecs. Compressing them again spends CPU to add bytes.
- Validators change under recompression. The origin object may carry a valid, strong ETag. When CloudFront compresses such an object, CloudFront rewrites the value as a weak ETag by prefixing W/. This way, the compressed and uncompressed forms remain semantically equivalent for conditional requests. An ETag that is neither quoted nor weak is dropped entirely.
- Range requests. Azure Front Door warns that byte-range requests may compress to different sizes, while it requires equal Content-Length values across GET requests. It returns 503 when they disagree. The documented remedies are to disable compression or to strip Accept-Encoding from range requests.
- Brotli is no longer automatically the best choice. Zstandard is a registered coding that browsers now advertise beside br, and it is Cloudflare's default on the Free plan. Measure against your own content and CPU budget rather than assuming Brotli wins.
Best practice
- Enable Brotli for text media types only. These are HTML, CSS, JavaScript, JSON, XML, SVG and uncompressed font formats. Never enable it for media that is already compressed.
- Precompress cacheable static assets in the build pipeline. Use the highest quality you can afford (11 is the reference encoder's own default), and serve those files. Reserve on-the-fly compression at a modest quality for dynamic and uncacheable responses.
- Send Vary: Accept-Encoding on every cacheable compressible response. Confirm the CDN's cache key honours it, so gzip and Brotli variants never cross. Let the CDN normalise the header rather than caching a variant per spelling.
- Respect your CDN's eligibility window, because objects outside it ship uncompressed silently. CloudFront needs 1,000 to 10,000,000 bytes and a Content-Length. Azure Front Door needs 1 KB to 8 MB and caching enabled on the route.
- Verify on the wire, not in the configuration screen. Request with Accept-Encoding: br and check that Content-Encoding: br comes back. Then repeat with Accept-Encoding: gzip, and confirm you get a separate, correct variant rather than the Brotli body.
- Measure the result on real traffic. Ideally, compare it against TTFB rather than transferred size alone. If on-the-fly encoding becomes the bottleneck, lower the quality, precompress more, or hand the small files to gzip.
Interactive Animation
Examples
Enable Brotli in Nginx:
# Nginx (requires ngx_brotli module)
brotli on;
brotli_comp_level 6;
brotli_types text/html text/css application/javascript application/json;
# Verify with curl
$ curl -H 'Accept-Encoding: br' -I https://example.com/style.css
Content-Encoding: br
Content-Length: 4521 # vs 5890 with gzip
Frequently Asked Questions
Brotli is a lossless compression format from Google, specified in IETF RFC 7932 and carried on the web as the "br" HTTP content coding. Google measured a 13-21% higher compression ratio than deflate at equal quality, paid for in encoder time, not decode speed. Every current major browser accepts it.
Enable Brotli in Nginx:
# Nginx (requires ngx_brotli module)
brotli on;
brotli_comp_level 6;
brotli_types text/html text/css application/javascript application/json;
# Verify with curl
$ curl -H 'Accept-Encoding: br' -I https://example.com/style.css
Content-Encoding: br
Content-Length: 4521 # vs 5890 with gzip
Yes. Brotli is also known as Brotli Compressed Data Format. Brotli is a lossless compression format from Google, specified in IETF RFC 7932 and carried on the web as the "br" HTTP content coding. Google measured a 13-21% higher compression ratio than deflate at equal quality, paid for in encoder time, not decode speed. Every current major browser accepts it.
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 …