Gzip vs Brotli: Compression and Web Performance
At the network edge, reducing payload size is one of the most effective ways to lower latency and save on cloud egress costs. Modern web architectures rely heavily on compression to shrink text-based assets like HTML, CSS, JavaScript, and JSON before transmitting them over HTTP.
While Gzip has been the industry standard for decades, Brotli has emerged as a highly optimized alternative designed specifically for web content. This guide compares Gzip and Brotli compression algorithms, details how to configure them in production, analyzes their CPU-to-bandwidth trade-offs, and shows how to avoid common caching pitfalls.
1. Protocol Negotiation Flow
HTTP compression is negotiated dynamically between the client browser and the server. The exchange operates via headers:
- Request: The browser advertises its supported compression algorithms using the
Accept-Encodingheader:Accept-Encoding: br, gzip, deflate - Response: The server selects the most efficient algorithm (preferring Brotli (
br) over Gzip (gzip)), compresses the response body, and returns the payload with theContent-Encodingheader:Content-Encoding: br Vary: Accept-Encoding
The Vary: Accept-Encoding header instructs downstream proxies and CDNs to cache separate representations of the asset based on the browser's supported compression formats, preventing Gzip-only browsers from receiving unreadable Brotli payloads.
2. Gzip vs Brotli Comparison
To select the right compression strategy for each asset type, we must evaluate their architectural properties:
| Feature | Gzip (DEFLATE) | Brotli |
|---|---|---|
| Core Algorithm | LZ77 dictionary matching + Huffman coding | LZ77 + Huffman coding + 2nd-order context modeling |
| Static Dictionary | No (generates dictionary dynamically per file) | Yes (120KB built-in dictionary of common web words) |
| Default Quality Range | 1 (Fastest) to 9 (Most Compressed) | 0 (Fastest) to 11 (Most Compressed) |
| Best Target Assets | Legacy browser fallbacks | HTML, JS, CSS, SVG, JSON API payloads |
| Egress Reduction | Moderate ((15% - 25%) typical) | High ((20% - 35%) typical, beats Gzip by (17% - 22%) on JS/CSS) |
3. The Math Behind Compression Economics
We define the compressed payload size ((B_c)) using the uncompressed size ((B_u)) and the compression ratio ((R)):
[B_c = B_u \cdot R]
Where the compression ratio is calculated as:
[R = \frac{B_c}{B_u}]
The bandwidth reduction percentage is represented by:
[\text{Reduction} = 1 - R]
At high request volumes, dynamic compression introduces CPU latency overhead. SREs estimate the dynamic compression processing cost ((CPU_{\text{compression}})) on the origin server as:
[CPU_{\text{compression}} = QPS \cdot C_{\text{compress}}(B_u)]
Where (QPS) is queries per second and (C_{\text{compress}}(B_u)) is the CPU time required to compress a payload of size (B_u). If you compress large dynamic JSON payloads at Brotli level 11 on the fly, origin CPU cores will saturate, driving up tail latency ((P_{99})) and increasing server infrastructure costs.
4. Production-Ready Nginx Configuration
To combine Gzip and Brotli efficiently, use this Nginx configuration snippet. It enables dynamic compression for APIs and matches static precompressed assets (prebuilt during your CI/CD build phase) to bypass runtime CPU overhead.
# Enable Gzip Compression
gzip on;
gzip_min_length 1024;
gzip_comp_level 5;
gzip_proxied any;
gzip_vary on;
gzip_types
text/plain
text/css
application/json
application/javascript
image/svg+xml;
# Enable Brotli Compression (Requires ngx_brotli module)
brotli on;
brotli_comp_level 4; # Optimized quality setting for dynamic compression
brotli_min_length 1024;
brotli_types
text/plain
text/css
application/json
application/javascript
image/svg+xml;
# Match Precompressed Static Files (.br and .gz) if they exist
gzip_static on;
brotli_static on;
5. CLI Verification and Benchmarking
SRE teams verify edge compression behavior using curl to inspect HTTP response headers:
curl -sS -D - \
-H 'Accept-Encoding: br, gzip' \
https://pingzoapp.com/assets/app.js \
-o /dev/null
Reproducible Local Benchmark Script
Create this simple bash script to compare how different encoding requests perform against your endpoints:
#!/usr/bin/env bash
set -euo pipefail
URL="https://pingzoapp.com/assets/app.js"
for encoding in "br" "gzip" "identity"; do
echo "=== Testing Encoding: $encoding ==="
curl -sS \
-H "Accept-Encoding: $encoding" \
-o "/dev/null" \
-w 'HTTP Status: %{http_code}\nSize: %{size_download} bytes\nTTFB: %{time_starttransfer}s\nTotal Time: %{time_total}s\n\n' \
"$URL"
done
[!TIP] Tip (HTTP Header Audit): Use the HTTP Header Checker to verify that your live endpoints are returning the correct
Vary: Accept-EncodingandContent-Encodingheaders, ensuring your CDN cache is not serving uncompressed assets to modern clients.
6. Troubleshooting Compression Issues
If your page size metrics indicate that compression is failing or inflating latency, execute this step-by-step diagnostic playbook:
- Inspect response headers: Verify if the server is returning
Content-Encoding: brorgzipwhen requested viaAccept-Encoding. - Verify content types: Ensure the asset's
Content-Typeis explicitly listed in your web server'sgzip_typesorbrotli_typesdirectives. - Check payload dimensions: Confirm that the uncompressed payload size is greater than your configured minimum threshold (typically
1024bytes). - Isolate cache validations: Verify if your server is stripping
ETagheaders or generating weak ETags after compression, which can break304 Not Modifiedbrowser cache validations. - Audit already-compressed assets: Ensure that pre-compressed media formats (like JPEG, WebP, PNG, ZIP, and WOFF2) are explicitly excluded from server-side compression, as recompressing them wastes CPU cycles and can increase final file size.
- Measure compression CPU time: Check if origin server CPU cores are bottlenecked. If dynamic Brotli compression is consuming excessive CPU, lower your dynamic quality setting to level 4 or shift compression to the edge CDN.
- Identify CDN cache fragmentation: Check if your edge cache keys are normalized. If the CDN is caching separate files for every variation of the
Accept-Encodingheader, your cache hit ratios will plummet. - Verify security bounds: Disable compression for sensitive API routes that mix user credentials with attacker-controlled inputs to prevent side-channel leaks (like BREACH).
- Confirm static assets: Ensure your build process precompresses static JavaScript and CSS files, verifying that Nginx is utilizing the static files rather than dynamically compressing them on the fly.