"Two of HTTP's oldest performance wins are still its biggest: compress the body, reuse the connection. Both are negotiated on the wire; both cost almost nothing to enable."
Content Compression — Negotiated, Transparent
HTTP body compression is negotiated via two headers:
Accept-Encoding(request) — what compressions the client can decode:gzip, br, zstd, identity.Content-Encoding(response) — what the server actually used.
Browsers always send Accept-Encoding. Servers pick the best supported encoding and compress the body before sending. Clients decompress transparently — the application code sees decoded text.
Three algorithms in active use:
- gzip — universally supported (every HTTP client and server). 50-70% reduction on text content. Fast encode/decode. The safe default.
- Brotli (br) — better compression (15-25% more than gzip on text), comparable decode speed, slower high-quality encode. Almost universally supported in 2026 (every modern browser + most CDNs).
- Zstandard (zstd) — newer, very fast at moderate quality, growing in HTTP support but not yet universal.
Pre-Compress Static, Compress-Per-Request Dynamic
The cost asymmetry between encoding and decoding matters:
- Static assets — pre-compress at build time with highest-quality Brotli. Ship the .br file directly when Accept-Encoding allows. One-time CPU cost amortized across every request.
- Dynamic responses — compress per-request with moderate quality gzip or Brotli (level 4-6). The CPU/bandwidth tradeoff usually favors compression for text/JSON over ~1KB.
Don't compress already-compressed content (images, video, .tar.gz) — gain is zero, CPU is wasted, and re-compression can sometimes increase size.
Keep-Alive — The Connection You Don't Throw Away
HTTP/1.0's default was "open a TCP connection, send one request, get one response, close." The TCP handshake (1 RTT) + TLS handshake (1-2 RTTs) ate every request. For a page with 60 assets, that's 180+ RTTs of pure connection setup.
HTTP/1.1's Connection: keep-alive (the default) keeps the TCP connection open after the response, letting subsequent requests reuse it. One handshake amortized over N requests. The numbers: a remote server at 100ms RTT goes from ~300ms per request (HTTP/1.0) to ~100ms per request (HTTP/1.1 keep-alive) without changing anything else.
HTTP/2 takes this further with multiplexing (next lesson) — one connection carries many parallel requests. HTTP/3 with QUIC adds connection migration (your phone can switch from Wi-Fi to LTE without losing the session).
The Connection-Header Gotchas
1. Connection: close on every response. Some legacy servers (or proxies) emit Connection: close after every request, forcing a new TCP setup. Look for it; fix it.
2. Long-lived connections leaking server resources. Server-side, keep-alive has a TTL (usually 60-300s). After the TTL, the server closes idle connections. Tune based on your client patterns.
3. SSL session resumption. Even with keep-alive, new connections incur TLS handshake. Modern TLS (1.3) supports session resumption — a returning client skips the key exchange. Make sure your server enables it.