Innovate | Connect | Transform
ContactService statusUSD $

GICT knowledge base

How to Enable Brotli Compression in Nginx on Ubuntu 24.04

Published

PerformancenginxUbuntu
Brotli makes text files smaller before Nginx sends them to the browser.

Brotli can reduce the transferred size of text assets such as HTML, CSS, JavaScript and JSON. Results depend on the input, compression settings and client support. Measure your own responses rather than assuming every file will be smaller or every page faster.

Nginx doesn’t include Brotli, but Ubuntu 24.04 packages it as two ready-made modules, so you don’t need to compile anything. Setting it up takes a few minutes, and gzip stays on for clients that don’t support Brotli.

Before you start

You need:

  • An Ubuntu 24.04 server running Nginx from Ubuntu’s own repository, with sudo access. If you need a server, an InterServer VPS works well for this.
  • A site served over HTTPS. Normal browser negotiation uses Brotli over secure connections; command-line clients can explicitly request encodings. Check actual request and response headers rather than inferring the encoding from the URL alone.

Check what the server sends now. Replace the address with a CSS or JavaScript file from your own site:

$ curl -s -o /dev/null -H 'Accept-Encoding: br, gzip' -w '%{size_download} bytes, encoding: %header{content-encoding}\n' https://example.com/css/style.css

On a default Ubuntu install the output shows the full file size and an empty encoding. Ubuntu’s nginx.conf turns gzip on, but only for HTML, so stylesheets, scripts and JSON go out uncompressed.

Install the Brotli modules

$ sudo apt update
$ sudo apt install libnginx-mod-http-brotli-filter libnginx-mod-http-brotli-static

The filter module compresses responses as they are sent. The static module serves files you have compressed in advance, which is covered later. The packages come from the universe repository, which Ubuntu enables by default.

The packages load both modules automatically. Confirm it:

$ sudo nginx -T 2>/dev/null | grep brotli_
load_module modules/ngx_http_brotli_filter_module.so;
load_module modules/ngx_http_brotli_static_module.so;

Don’t use nginx -V | grep brotli to check. It only lists features compiled into Nginx itself, so it shows nothing for modules loaded this way.

Turn on Brotli and fix the gzip fallback

Create the file /etc/nginx/conf.d/compression.conf with the following content. Nginx reads every .conf file in this folder inside its http block, so the settings apply to all your sites.

# Brotli
brotli on;
brotli_static on;
brotli_comp_level 5;
brotli_min_length 256;
brotli_types text/plain text/css text/xml text/javascript
             application/javascript application/json application/xml
             application/rss+xml application/atom+xml application/xhtml+xml
             image/svg+xml font/ttf font/otf application/vnd.ms-fontobject
             application/wasm;

# gzip for clients without Brotli ("gzip on" is already in nginx.conf)
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/xml
           application/rss+xml application/atom+xml application/xhtml+xml
           image/svg+xml font/ttf font/otf application/vnd.ms-fontobject
           application/wasm;

What the main settings do:

  • brotli_comp_level 5 balances file size against CPU time. Levels 4 to 6 suit compression on the fly. Levels 10 and 11 are far slower and are meant for compressing files in advance; on the fly they can use up a small server’s CPU under modest traffic.
  • brotli_min_length 256 skips tiny responses, where compression saves almost nothing.
  • brotli_types selects additional MIME types. text/html is included by default, but eligibility still depends on response size, encoding negotiation and other module conditions. JPEG, PNG and WebP are already compressed and are deliberately omitted.
  • gzip_vary on adds the Vary: Accept-Encoding header to both Brotli and gzip responses. The Brotli module doesn’t send this header by itself. Without it, a CDN or caching proxy could store a Brotli response and hand it to a client that can’t read it.

The gzip lines extend Ubuntu’s HTML-only default to the same file types. Don’t add gzip on; here, because nginx.conf already sets it and Nginx refuses to start with the directive twice.

Test the configuration and reload Nginx:

$ sudo nginx -t
$ sudo systemctl reload nginx

Check that Brotli works

Request the same file three ways: as a Brotli client, as a gzip-only client and with no compression.

$ for enc in br gzip identity; do curl -s -o /dev/null -H "Accept-Encoding: $enc" -w "$enc: %{size_download} bytes, encoding: %header{content-encoding}\n" https://example.com/css/style.css; done

In a browser, open the developer tools, go to the Network tab and reload the page. Click a CSS or JavaScript file. Its response headers should show content-encoding: br.

Compare the sizes recorded for your own representative assets, using the same file revision and a successful response for each encoding. Also check CPU use and latency under representative load; transfer size alone is not a performance benchmark.

Pre-compress static files for the smallest size

For assets that change only at deployment, generate a matching .br file once. Serving it avoids per-request compression work, though normal file serving still uses resources. The brotli_static on; directive enables this path. Install the command-line tool:

$ sudo apt install brotli

Compress the CSS, JavaScript, SVG and JSON files under your web root. The command keeps the originals and writes a .br file next to each:

$ sudo find /var/www/html -type f \( -name '*.css' -o -name '*.js' -o -name '*.svg' -o -name '*.json' \) -exec brotli --best --keep --force {} \;

Higher compression levels trade build-time CPU for potentially smaller files. Measure the resulting size and build time on your assets; there is no fixed saving over on-the-fly compression.

Nginx serves the .br file whenever one exists, even if the original has changed since. Run the find command again after every deploy, preferably as part of your deploy script. Otherwise visitors get the old version of the file.

Apps behind a reverse proxy

If Nginx forwards requests to an application with proxy_pass, Brotli also compresses the application’s responses. One exception: if the application compresses its own responses, Nginx passes its gzip output through unchanged, and browsers get gzip instead of Brotli. Tell the application not to compress by adding this line next to proxy_pass in the location block:

proxy_set_header Accept-Encoding "";

Then test and reload Nginx again.

Troubleshooting

unknown directive "brotli" when testing the configuration. The module isn’t loaded. Install both packages again and check that /etc/nginx/modules-enabled/ contains the two 50-mod-http-brotli-*.conf links.

"gzip" directive is duplicate. Remove gzip on; from compression.conf; nginx.conf already contains it.

The browser shows gzip, but curl shows br: compare the actual URL, Accept-Encoding request header, cached response and any CDN/proxy. Secure browser negotiation is one factor, not the only possible cause.

A file isn’t compressed at all. Check its type with curl -sI and look at Content-Type. Add the type to brotli_types and gzip_types if it’s missing. Files smaller than 256 bytes are skipped on purpose.

Visitors see an old stylesheet or script after a deploy. A stale .br file is being served. Run the brotli command again, or delete the old .br files.

Reference: ngx_brotli module documentation. Check the documentation for your installed version before applying changes.

Related guides

Account services are unavailable until the production backend is configured.