Compression vs encoding: gzip, deflate and Brotli
Content coding is a representation choice, not encryption, how negotiation works over HTTP, why compressing secrets can leak them, and how to choose an algorithm.
Encoding is not secrecy
Base64, percent-encoding and gzip all transform bytes so they can travel through a channel. None of them hides anything. Only encryption provides confidentiality, and it is a separate layer.
| Transform | Purpose | Reversible without a key | Changes size |
|---|---|---|---|
| Base64 | Represent binary as ASCII | Yes | Up about 33 percent |
| Percent-encoding | Make bytes safe in a URL | Yes | Up, sometimes 3x |
| gzip | Reduce transfer size | Yes | Down about 60-80 percent for text |
| Brotli | Reduce transfer size further | Yes | Down more, slower to compress |
| AES-GCM | Confidentiality and integrity | No | Up by a small constant |
Negotiating a content coding
GET /app.js HTTP/1.1
Accept-Encoding: br, gzip, deflate
Host: example.com
HTTP/1.1 200 OK
Content-Encoding: br
Content-Type: application/javascript
Vary: Accept-Encoding
Content-Length: 48213
<compressed bytes>Accept-Encodinglists what the client can decode, with optional quality values.Content-Encodingdescribes what was actually applied to this representation.Vary: Accept-Encodingis mandatory — without it a cache may serve a compressed body to a client that cannot decode it.- Never compress an already-compressed payload: images, video and archives grow slightly and waste CPU.
- A compressed response can be served from a cache keyed on the coding, which is why the
Varyheader is not optional.
# compare algorithms on a real file
for a in gzip br zstd; do
printf '%-6s ' "$a"
case $a in
gzip) gzip -9 -c app.js | wc -c ;;
br) brotli -q 11 -c app.js | wc -c ;;
zstd) zstd -19 -c app.js | wc -c ;;
esac
done
wc -c app.js| Algorithm | Typical ratio on text | Compression cost | Support |
|---|---|---|---|
| deflate | Baseline 100 percent | Lowest | Universal |
| gzip | Same as deflate with headers | Low | Universal |
| Brotli | 15-25 percent smaller than gzip | Higher, especially at quality 11 | All modern browsers |
| zstd | Comparable to Brotli, much faster | Low at high ratios | Growing, weaker in browsers |
When compression leaks
Compression reveals structure through size. If an attacker can influence part of a secret-bearing payload and observe the compressed length, they can recover the secret byte by byte. This is the CRIME and BREACH class of attacks.
- Never compress a response that mixes attacker-controlled input with a secret, such as a session token.
- Disable compression on endpoints that reflect a secret in the body with an attacker-supplied prefix.
- Add random padding or a per-response random prefix so the length is not a stable signal.
- Set
Cache-Control: no-storeon such endpoints so no shared cache retains them. - Compression at rest is a different risk: it saves space and does not help an attacker who cannot measure length.
💡
Turn off compression for a response if the risk of a length oracle outweighs the bandwidth saving. A few kilobytes are cheaper than a leaked authentication token, and the endpoints that need this protection are usually small JSON responses anyway.
FAQ
Is Brotli always better than gzip?
For static assets precompressed at build time, yes — smaller with no runtime cost. For dynamic responses the higher compression cost can exceed the bandwidth saved, so gzip or zstd often wins.
Does compression help JSON APIs?
Usually a lot, since JSON is repetitive text. Enable it above a size threshold, skip it for tiny payloads where headers dominate, and keep the
Vary header correct.Related
Quoted-printable, MIME and email encoding Mojibake: diagnosing and fixing broken text
Last refreshed 2026-09-18.