Text files — HTML, CSS, JavaScript, JSON, SVG — compress extremely well, often to 20–30% of their size. Compressing them on the way out makes pages load faster, especially on mobile connections, and cuts your bandwidth use. Nginx supports gzip out of the box and Brotli through a module.
Enable gzip
Ubuntu’s nginx.conf already contains gzip on; but compresses only text/html by default. Create /etc/nginx/conf.d/gzip.conf:
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 5;
gzip_min_length 256;
gzip_types
text/plain
text/css
text/xml
text/javascript
application/javascript
application/json
application/ld+json
application/manifest+json
application/rss+xml
application/atom+xml
application/xml
image/svg+xml
font/ttf
font/otf
application/vnd.ms-fontobject;
If nginx.conf already has a gzip on; line, either remove it there or omit it here — a duplicate directive in the same context makes nginx -t fail.
sudo nginx -t && sudo systemctl reload nginx
What the settings do:
gzip_types— MIME types to compress.text/htmlis always included, so don’t list it (Nginx warns about duplicates).gzip_comp_level 5— levels 1–9; above 5–6, files barely shrink further while CPU cost keeps rising.gzip_min_length 256— tiny responses aren’t worth compressing.gzip_vary on— addsVary: Accept-Encoding, so caches store compressed and uncompressed versions separately.gzip_proxied any— also compress responses to requests that came through a proxy or CDN.
Don’t compress JPEG, PNG, WebP, AVIF, MP4, WOFF2 or ZIP files — they’re already compressed, and gzip just wastes CPU.
Add Brotli
Brotli typically produces files 15–25% smaller than gzip for text, and every current browser supports it over HTTPS. Nginx needs the ngx_brotli module.
On Debian and recent Ubuntu, packaged dynamic modules exist:
sudo apt install libnginx-mod-http-brotli-filter libnginx-mod-http-brotli-static
(Package names and availability vary by release; if they’re missing, the nginx.org packages or a build of the Google ngx_brotli module against your exact Nginx version are the alternatives.)
The packages enable the modules automatically via /etc/nginx/modules-enabled/. Then configure, e.g. in /etc/nginx/conf.d/brotli.conf:
brotli on;
brotli_comp_level 5;
brotli_min_length 256;
brotli_types
text/plain text/css text/xml text/javascript
application/javascript application/json application/ld+json
application/manifest+json application/rss+xml application/xml
image/svg+xml font/ttf font/otf application/vnd.ms-fontobject;
Keep gzip enabled too: Nginx serves Brotli to clients that send Accept-Encoding: br and gzip to the rest.
Pre-compressed static files
Compressing on every request costs CPU. For static assets that don’t change between deploys, compress once at build time and let Nginx serve the ready-made file:
location /assets/ {
gzip_static on; # serves app.js.gz if it exists
brotli_static on; # serves app.js.br if it exists (needs the static module)
expires 1y;
add_header Cache-Control "public, immutable";
}
Create the files during your build:
find dist/assets -type f \( -name '*.js' -o -name '*.css' -o -name '*.svg' \) \
-exec gzip -k -9 {} \; -exec brotli -k -q 11 {} \;
With pre-compressed files you can use maximum compression levels, since the cost is paid once.
Verify
curl -s -o /dev/null -w "%{size_download} bytes\n" https://example.com/styles.css
curl -s -o /dev/null -w "%{size_download} bytes\n" -H "Accept-Encoding: gzip" https://example.com/styles.css
curl -sI -H "Accept-Encoding: br" https://example.com/styles.css | grep -i content-encoding
The second number should be much smaller, and the last command should print content-encoding: br. In browser developer tools, the Network tab shows Content-Encoding per request.
Compression and proxies/CDNs
- If you use Cloudflare or another CDN, it compresses for visitors itself; your origin compression still saves bandwidth between origin and CDN.
- When Nginx proxies to an app that already compresses, Nginx passes the compressed response through. Usually it’s better to turn compression off in the app and let Nginx do it.
Security note: BREACH
Compressing HTTPS responses that contain both secrets (CSRF tokens) and attacker-controlled input can, in theory, allow the BREACH attack. Most frameworks mitigate it by masking tokens per request. It’s not a reason to disable compression for typical sites.
Results to expect
| File | Original | gzip (5) | Brotli (5) |
|---|---|---|---|
| Framework CSS | 190 KB | ~26 KB | ~21 KB |
| JS bundle | 450 KB | ~140 KB | ~120 KB |
| HTML page | 60 KB | ~12 KB | ~10 KB |
Figures vary, but compression is one of the cheapest speed wins available. Combine it with browser caching and see website speed optimization for more. Faster pages also mean less transfer — welcome on any VPS plan.