How to Enable Gzip and Brotli Compression in Nginx
Practice3 minInnovialabs editorial team
In short
Gzip is enabled with the built-in gzip on and gzip_types directives, Brotli needs the ngx_brotli module; compress text formats at a mid level, precompress static files and verify via the Content-Encoding header.
How compression works
In every request the browser says which algorithms it understands: Accept-Encoding: gzip, deflate, br. The server picks one, compresses the response and marks it with a Content-Encoding header. The browser decompresses the data itself.
Text formats benefit the most: HTML, CSS, JavaScript, JSON, SVG. Brotli usually compresses text more tightly than gzip, while gzip is supported by practically every client. So it makes sense to enable both: Brotli for modern browsers, gzip as the fallback.
Configuring gzip
Gzip is built into Nginx. Add to the http block:
gzip on;
gzip_comp_level 5;
gzip_min_length 256;
gzip_vary on;
gzip_proxied any;
gzip_types
text/plain
text/css
text/xml
application/javascript
application/json
application/xml
image/svg+xml;
What matters here:
- gzip_comp_level — from 1 to 9. A higher level costs more CPU for a small size gain. For on-the-fly compression, mid values are the usual choice.
- gzip_min_length — very small responses are not worth compressing.
- gzip_vary — adds
Vary: Accept-Encodingso CDNs and proxies do not serve a compressed copy to a client that cannot read it. - gzip_proxied any — compress responses to requests that came through a proxy or CDN as well.
- text/html does not need to be listed in
gzip_types: it is always compressed.
Configuring Brotli
Brotli is not part of the standard Nginx build. You need the ngx_brotli module: some distributions ship it as a package, otherwise it is built as a dynamic module for your Nginx version. Once installed, load the modules at the top of nginx.conf:
load_module modules/ngx_http_brotli_filter_module.so;
load_module modules/ngx_http_brotli_static_module.so;
And in the http block:
brotli on;
brotli_comp_level 5;
brotli_types
text/plain
text/css
text/xml
application/javascript
application/json
application/xml
image/svg+xml;
If the client supports both algorithms, Brotli is used; if only gzip, then gzip.
What not to compress
JPEG, PNG, WebP, AVIF, MP4, WOFF2 and archives are already compressed. Compressing them again wastes CPU and barely reduces size. That is why you list types explicitly instead of compressing everything.
Precompressed files
For static files (JS and CSS after the build), it is better to compress them ahead of time at the maximum level and let Nginx simply serve the result:
gzip -k -9 dist/assets/*.js dist/assets/*.css
brotli -q 11 dist/assets/*.js dist/assets/*.css
Next to app.js you get app.js.gz and app.js.br. Enable serving them:
gzip_static on;
brotli_static on;
Nginx checks whether a file with the right extension exists and serves it without compressing on the fly. gzip_static requires the ngx_http_gzip_static_module; nginx -V shows whether your build includes it.
How to verify
sudo nginx -tandsudo systemctl reload nginx.- Send a request with the header:
curl -s -o /dev/null -D - -H "Accept-Encoding: br" https://example.com/assets/app.js
curl -s -o /dev/null -D - -H "Accept-Encoding: gzip" https://example.com/assets/app.js
Look for content-encoding: br or content-encoding: gzip in the response.
- In browser DevTools, enable the Content-Encoding column in the Network tab and compare transferred and actual file sizes.
Common mistakes
- A missing MIME type. For example, the API returns
application/json, but it is not ingzip_types. - Brotli module not loaded. The
brotlidirective without the module makesnginx -tfail. - Maximum level on the fly. It loads the CPU on every request; keep the maximum for precompressed static files.
- Double compression. If the backend already compresses responses and a CDN sits in front, check where compression happens and keep it in one place.