HTTP compression shrinks a response body to cut how much travels over the wire. Text like HTML, CSS, JS, and JSON is highly repetitive and typically shrinks by 70-90%, which materially affects page load speed and bandwidth cost. It works by client and server negotiating a supported scheme, the server compressing the body and stating which scheme it used, and the client decompressing to consume it.
Negotiation: Accept-Encoding and Content-Encoding
The client lists the schemes it can decompress in the Accept-Encoding request header; the server picks one, compresses the body, and declares the scheme it actually used in the Content-Encoding response header. That negotiation must be marked with Vary: Accept-Encoding so caches keep compressed and uncompressed copies apart.
GET /app.js HTTP/1.1
Host: example.com
Accept-Encoding: br, gzip, deflate
HTTP/1.1 200 OK
Content-Type: application/javascript
Content-Encoding: br
Vary: Accept-Encoding
Content-Length: 24518
Comparing the algorithms
The common algorithms strike different balances between ratio and speed.
- gzip: The near-universal de facto standard (DEFLATE-based). A good ratio/speed balance with perfect compatibility — always support it as the reliable fallback.
- Brotli (
br): Google's algorithm, compressing static assets roughly 15-25% smaller than gzip. Its top levels are slow to compress, which suits pre-compressed static files especially well. Most modern browsers support it over HTTPS. - deflate: The same DEFLATE algorithm as gzip but wrapped differently, which trips up some implementations. In practice people use gzip and skip deflate.
- zstd (Zstandard): Facebook's newer algorithm, hitting Brotli-class ratios at much higher speed — attractive for compressing dynamic content, with browser and CDN support expanding.
What and when to compress
Compression isn't free or universal. Don't recompress already-compressed binaries. Files like JPEG, PNG, WebP, MP4, zip, and gz are internally compressed already; re-compressing barely helps (or grows the file) while wasting CPU. Compression pays off on text: HTML, CSS, JS, JSON, XML, SVG, and some fonts. Also, very small responses (a few hundred bytes) let the compression overhead cancel the gain, so set a minimum-size threshold around 1KB and compress only text above it.
Security risks: CRIME and BREACH
Compression carries a subtle security risk. Because compressed output shrinks more when data repeats, an attacker who tweaks a request and watches the resulting response (or request) size can guess a secret inside it (a session or CSRF token) one character at a time. CRIME targeted request-header compression (TLS/SPDY level); BREACH targets HTTP response-body compression. Defenses include (1) not reflecting a sensitive secret into a compressed response body alongside user input, (2) randomizing (masking) the CSRF token per request, and (3) hiding length via random padding. These application-level measures are more practical than turning gzip off entirely.
Content-Encoding vs Transfer-Encoding
Two easily confused headers must be told apart. Content-Encoding means the resource's representation itself is compressed — an end-to-end property. What a cache stores is the compressed body, and the ETag reflects the compressed representation. In contrast, Transfer-Encoding: chunked (or gzip) is a hop-by-hop property describing only how the body is framed on this connection, used to send a body in pieces when its length isn't known up front. For real-world web performance, compression is almost always Content-Encoding, while Transfer-Encoding is best understood as a streaming transfer mechanism.