Why Compression Matters for Web Performance
Every byte your server sends to a browser must travel across the network. For a visitor on the other side of the world, that journey involves dozens of network hops, undersea cables, and cellular towers. The larger your response, the longer the journey takes. HTTP compression reduces the size of text-based responses — HTML, CSS, JavaScript, JSON, SVG, and XML — typically by 60-85%, making pages load dramatically faster without any change to the content itself.
Two compression algorithms dominate the web today: Gzip and Brotli. Gzip has been the standard since the late 1990s and is supported by literally every browser and web server in existence. Brotli, developed by Google and released in 2015, achieves significantly better compression ratios, especially for text content. The question is not which one to use — the answer is both. The real question is how to configure them optimally for your specific workload.
This guide provides a comprehensive comparison of Gzip and Brotli, with Nginx and Apache configuration examples, real-world benchmarks on different content types, and guidance on choosing the right compression levels for your server.
How HTTP Compression Works
HTTP compression is a negotiation between the browser and the server. The process works like this:
Accept-Encoding: gzip, br
Supported algorithms
Best mutual algorithm
Content-Encoding: br
Transparent to user
GET /app.js HTTP/2
Host: example.com
Accept-Encoding: gzip, deflate, br
# Server responds with compressed content
HTTP/2 200 OK
Content-Encoding: br
Vary: Accept-Encoding
Content-Type: application/javascript
Content-Length: 42518
# (Original uncompressed size: 186432 bytes)
Vary: Accept-Encoding in the response. This tells CDNs and proxy caches that different versions of the response exist for different Accept-Encoding values. Without it, a cache might serve a Brotli-compressed response to a client that only supports Gzip, resulting in garbled content.
Gzip: The Universal Standard
Gzip uses the DEFLATE algorithm (a combination of LZ77 and Huffman coding) and has been part of the HTTP standard since HTTP/1.1. It is supported by every browser released in the past 25 years and by every web server, proxy, and CDN.
Gzip Configuration in Nginx
gzip on;
# Compression level: 1-9 (1=fastest, 9=smallest)
gzip_comp_level 6;
# Minimum response size to compress
gzip_min_length 256;
# Enable for proxied requests
gzip_proxied any;
# Add Vary header automatically
gzip_vary on;
# MIME types to compress
gzip_types
text/plain
text/css
text/javascript
text/xml
application/json
application/javascript
application/x-javascript
application/xml
application/xml+rss
application/atom+xml
application/ld+json
application/manifest+json
application/vnd.ms-fontobject
font/opentype
font/ttf
image/svg+xml
image/x-icon;
# Disable for IE6 (ancient, but just in case)
gzip_disable "msie6";
gzip_types.
Understanding gzip_comp_level
The compression level setting is where the performance trade-off happens. Higher levels produce smaller output but consume more CPU time. Here is the actual impact for a typical JavaScript file:
| gzip_comp_level | Compressed Size | Compression Ratio | CPU Time | Recommendation |
|---|---|---|---|---|
| 1 | 68.2 KB | 63.4% | 0.8ms | Too little compression |
| 2 | 65.1 KB | 65.1% | 1.0ms | |
| 3 | 62.8 KB | 66.3% | 1.3ms | |
| 4 | 58.4 KB | 68.7% | 1.8ms | Good for high-traffic |
| 5 | 56.2 KB | 69.8% | 2.5ms | |
| 6 | 54.8 KB | 70.6% | 3.2ms | Best balance (default) |
| 7 | 54.1 KB | 71.0% | 5.1ms | |
| 8 | 53.8 KB | 71.1% | 8.4ms | |
| 9 | 53.6 KB | 71.2% | 14.2ms | Diminishing returns |
Brotli: Better Compression for the Modern Web
Brotli was developed by Google specifically for web content compression. It uses a combination of LZ77, Huffman coding, and a predefined dictionary of common web content patterns (HTML tags, CSS properties, JavaScript keywords). This built-in dictionary gives Brotli a significant advantage over Gzip for web content — it does not need to "learn" common patterns from the data itself because they are already in its dictionary.
Installing the Brotli Module for Nginx
Unlike Gzip, which is built into Nginx by default, Brotli requires a separate module. On most Linux distributions, you can install it as a dynamic module:
$ apt install libnginx-mod-http-brotli-filter libnginx-mod-http-brotli-static
# Verify module is loaded
$ nginx -V 2>&1 | grep -o 'brotli'
brotli
# If compiling Nginx from source, add:
--add-module=/path/to/ngx_brotli
Brotli Configuration in Nginx
brotli on;
# Compression level: 0-11 (0=fastest, 11=smallest)
brotli_comp_level 4;
# Minimum response size to compress
brotli_min_length 256;
# MIME types to compress (same as gzip_types)
brotli_types
text/plain
text/css
text/javascript
text/xml
application/json
application/javascript
application/x-javascript
application/xml
application/xml+rss
application/atom+xml
application/ld+json
application/manifest+json
application/vnd.ms-fontobject
font/opentype
font/ttf
image/svg+xml
image/x-icon;
# Serve pre-compressed .br files if available
brotli_static on;
Understanding brotli_comp_level
Brotli has a wider range of compression levels (0-11) than Gzip (1-9). The trade-off between compression ratio and CPU time is more dramatic with Brotli, especially at the highest levels:
| brotli_comp_level | Compressed Size | Compression Ratio | CPU Time | Recommendation |
|---|---|---|---|---|
| 0 | 66.4 KB | 64.4% | 0.5ms | Barely better than no compression |
| 1 | 58.7 KB | 68.5% | 0.7ms | Fast, decent compression |
| 2 | 53.2 KB | 71.4% | 1.1ms | |
| 3 | 50.8 KB | 72.7% | 1.5ms | |
| 4 | 48.1 KB | 74.2% | 2.8ms | Best for on-the-fly |
| 5 | 46.3 KB | 75.1% | 4.5ms | |
| 6 | 45.2 KB | 75.7% | 7.8ms | Good trade-off |
| 7-9 | 43.8-42.1 KB | 76.5-77.4% | 15-45ms | Only for static pre-compression |
| 10 | 40.5 KB | 78.3% | 120ms | Only for static pre-compression |
| 11 | 39.2 KB | 78.9% | 450ms | Pre-compress only |
Apache Configuration
Apache mod_deflate (Gzip)
$ a2enmod deflate
# Apache configuration
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE
text/html
text/plain
text/css
text/javascript
text/xml
application/json
application/javascript
application/xml
image/svg+xml
font/opentype
font/ttf
# Compression level (1-9)
DeflateCompressionLevel 6
# Skip already-compressed formats
SetEnvIfNoCase Request_URI \.(?:gif|jpe?g|png|webp|avif)$ no-gzip
</IfModule>
Apache mod_brotli
$ a2enmod brotli
# Apache configuration
<IfModule mod_brotli.c>
AddOutputFilterByType BROTLI_COMPRESS
text/html
text/plain
text/css
text/javascript
text/xml
application/json
application/javascript
application/xml
image/svg+xml
font/opentype
font/ttf
# Quality level (0-11)
BrotliCompressionQuality 4
</IfModule>
Benchmark Results: Gzip vs Brotli
We benchmarked both algorithms against common web content types at their recommended dynamic compression levels (Gzip 6 vs Brotli 4) and Brotli's maximum static compression level (11). All tests used real-world files from popular websites and frameworks.
HTML (WordPress homepage, 52 KB original)
| Algorithm | Compressed Size | Ratio | Time |
|---|---|---|---|
| Gzip level 6 | 11.8 KB | 77.3% | 1.2ms |
| Brotli level 4 | 9.4 KB | 81.9% | 1.8ms |
| Brotli level 11 | 7.9 KB | 84.8% | 85ms |
CSS (Bootstrap 5, 227 KB original)
| Algorithm | Compressed Size | Ratio | Time |
|---|---|---|---|
| Gzip level 6 | 33.4 KB | 85.3% | 3.8ms |
| Brotli level 4 | 27.1 KB | 88.1% | 4.5ms |
| Brotli level 11 | 22.8 KB | 90.0% | 320ms |
JavaScript (React bundle, 186 KB original)
| Algorithm | Compressed Size | Ratio | Time |
|---|---|---|---|
| Gzip level 6 | 54.8 KB | 70.5% | 3.2ms |
| Brotli level 4 | 48.1 KB | 74.1% | 2.8ms |
| Brotli level 11 | 39.2 KB | 78.9% | 450ms |
JSON (API response, 84 KB original)
| Algorithm | Compressed Size | Ratio | Time |
|---|---|---|---|
| Gzip level 6 | 12.6 KB | 85.0% | 1.4ms |
| Brotli level 4 | 10.2 KB | 87.9% | 1.6ms |
| Brotli level 11 | 8.1 KB | 90.4% | 95ms |
Pre-Compression for Static Files
The best of both worlds: compress static assets at maximum quality during your build process, and serve the pre-compressed files directly. This gives you Brotli level 11 compression without any runtime CPU cost.
# Brotli (maximum compression)
$ find /var/www/static -type f \( -name "*.css" -o -name "*.js" \
-o -name "*.html" -o -name "*.svg" -o -name "*.json" \) \
-exec brotli --best --keep {} \;
# Gzip (for fallback)
$ find /var/www/static -type f \( -name "*.css" -o -name "*.js" \
-o -name "*.html" -o -name "*.svg" -o -name "*.json" \) \
-exec gzip -9 --keep {} \;
# Result: three versions of each file
$ ls -la app.*
-rw-r--r-- 186432 app.js
-rw-r--r-- 39218 app.js.br
-rw-r--r-- 53612 app.js.gz
Nginx Configuration for Pre-Compressed Files
# Brotli: serve .br files if they exist
brotli_static on;
# Gzip: serve .gz files if they exist
gzip_static on;
# Priority: Nginx checks for .br first (if browser supports it),
# then .gz, then serves uncompressed original.
# Zero CPU — just file system lookups.
CDN Compression
Most CDNs handle compression automatically. Cloudflare, for example, compresses content at the edge regardless of whether your origin sends compressed responses. However, understanding how CDN compression interacts with your origin configuration prevents surprises:
Cloudflare Compression
Cloudflare always supports Gzip and Brotli at the edge. If your origin sends uncompressed content, Cloudflare compresses it. If your origin sends Gzip-compressed content, Cloudflare may decompress and recompress with Brotli for browsers that support it. For best results, let Cloudflare handle compression and send uncompressed from origin — or pre-compress with Brotli so Cloudflare can serve it directly.
Best Practice
Enable compression on your origin server even when using a CDN. This ensures that cache misses (first request to a PoP) receive compressed responses, and it protects your bandwidth if any requests bypass the CDN. The CDN's compression supplements, not replaces, origin compression.
Testing Compression
$ curl -sI -H "Accept-Encoding: gzip" https://example.com/style.css \
| grep -i content-encoding
content-encoding: gzip
# Test Brotli support
$ curl -sI -H "Accept-Encoding: br" https://example.com/style.css \
| grep -i content-encoding
content-encoding: br
# Compare actual transfer sizes
$ curl -so /dev/null -w "Size: %{size_download} bytes\n" \
-H "Accept-Encoding: gzip" https://example.com/app.js
Size: 54821 bytes
$ curl -so /dev/null -w "Size: %{size_download} bytes\n" \
-H "Accept-Encoding: br" https://example.com/app.js
Size: 48134 bytes
# No compression (original size)
$ curl -so /dev/null -w "Size: %{size_download} bytes\n" \
-H "Accept-Encoding: identity" https://example.com/app.js
Size: 186432 bytes
Browser Support
| Algorithm | Chrome | Firefox | Safari | Edge | Overall |
|---|---|---|---|---|---|
| Gzip | All | All | All | All | 100% |
| Brotli | 49+ | 44+ | 11+ | 15+ | 97%+ |
Brotli is supported by virtually every modern browser. The only browsers without Brotli support are Internet Explorer (all versions) and very old mobile browsers. Since Brotli requires HTTPS (it is only sent over secure connections), any site running HTTPS can safely enable Brotli with Gzip as automatic fallback.
Combined Configuration: The Complete Setup
Here is the recommended configuration that enables both Gzip and Brotli, with pre-compressed file support and optimal compression levels:
http {
# === GZIP ===
gzip on;
gzip_comp_level 6;
gzip_min_length 256;
gzip_vary on;
gzip_proxied any;
gzip_static on; # Serve pre-compressed .gz files
gzip_types
text/plain text/css text/javascript text/xml
application/json application/javascript
application/xml application/xml+rss
application/atom+xml application/ld+json
application/manifest+json
application/vnd.ms-fontobject
font/opentype font/ttf
image/svg+xml image/x-icon;
# === BROTLI ===
brotli on;
brotli_comp_level 4;
brotli_min_length 256;
brotli_static on; # Serve pre-compressed .br files
brotli_types
text/plain text/css text/javascript text/xml
application/json application/javascript
application/xml application/xml+rss
application/atom+xml application/ld+json
application/manifest+json
application/vnd.ms-fontobject
font/opentype font/ttf
image/svg+xml image/x-icon;
}
Accept-Encoding header. If the browser supports br (Brotli), Nginx uses Brotli. If the browser only supports gzip, Nginx falls back to Gzip. This is automatic — no additional configuration needed. For pre-compressed files, Nginx checks for .br files first, then .gz, then compresses on-the-fly.
When to Use Which
Use Gzip When...
- You need universal compatibility (100% browser support)
- Your server cannot install the Brotli module
- You are behind a proxy/CDN that does not forward Brotli
- HTTP-only sites (Brotli requires HTTPS)
Use Brotli When...
- Your site uses HTTPS (required for Brotli)
- You want the best compression ratios
- You serve large JavaScript/CSS bundles
- Mobile performance is a priority (smaller = faster)
Summary: Quick Reference
| Setting | Gzip | Brotli |
|---|---|---|
| Dynamic compression level | 6 | 4 |
| Static pre-compression level | 9 | 11 |
| Minimum response size | 256 bytes | 256 bytes |
| Typical HTML compression | 77% | 82% |
| Typical CSS compression | 85% | 88% |
| Typical JS compression | 71% | 74% |
| Browser support | 100% | 97%+ |
| Requires HTTPS | No | Yes |
The bottom line: enable both. Gzip provides universal coverage, Brotli provides superior compression for the 97%+ of browsers that support it. With pre-compression for static assets and on-the-fly compression for dynamic content, your server delivers the smallest possible responses to every visitor regardless of their browser. The bandwidth savings pay for themselves in faster load times, lower hosting costs, and happier users.