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.

TransformPurposeReversible without a keyChanges size
Base64Represent binary as ASCIIYesUp about 33 percent
Percent-encodingMake bytes safe in a URLYesUp, sometimes 3x
gzipReduce transfer sizeYesDown about 60-80 percent for text
BrotliReduce transfer size furtherYesDown more, slower to compress
AES-GCMConfidentiality and integrityNoUp 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-Encoding lists what the client can decode, with optional quality values.
  • Content-Encoding describes what was actually applied to this representation.
  • Vary: Accept-Encoding is 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 Vary header 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
AlgorithmTypical ratio on textCompression costSupport
deflateBaseline 100 percentLowestUniversal
gzipSame as deflate with headersLowUniversal
Brotli15-25 percent smaller than gzipHigher, especially at quality 11All modern browsers
zstdComparable to Brotli, much fasterLow at high ratiosGrowing, 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-store on 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.

Quoted-printable, MIME and email encoding Mojibake: diagnosing and fixing broken text

Last refreshed 2026-09-18.