--- title: "How to Enable Gzip and Brotli in Nginx and Apache on Ubuntu" description: "Enable gzip and Brotli compression in Nginx or Apache on Ubuntu 24.04, serve precompressed assets and verify with curl that text responses shrink by 70-80%." category: "Performance Optimization" language: en source: https://cubepath.com/docs/performance-optimization/gzip-brotli-compression-in-apache-and-nginx updated: 2026-09-25 --- # How to Enable Gzip and Brotli in Nginx and Apache on Ubuntu HTTP compression shrinks text responses (HTML, CSS, JavaScript, JSON, SVG) before they leave the server, which cuts transfer size and page load time with almost no effort. Gzip is supported by every client, and Brotli, which every modern browser supports over HTTPS, usually produces files 15-25% smaller than gzip at a similar CPU cost. In this tutorial you will measure a baseline, enable gzip and Brotli in either Nginx or Apache on Ubuntu 24.04, optionally serve precompressed files, and verify the result with `curl`. ## Prerequisites To follow this tutorial, you will need: - A server running Ubuntu 24.04 LTS, for example a CubePath VPS. - A non-root user with `sudo` privileges. - Nginx or Apache already installed and serving files from `/var/www/html`. Follow only the section for the web server you use. - For Brotli in real browsers: HTTPS on your site. Browsers only advertise `br` in `Accept-Encoding` over HTTPS, although `curl` can test it over plain HTTP. ## Step 1 - Creating a test file and measuring the baseline To see the effect of compression you need a realistic text file. Download the uncompressed Bootstrap stylesheet into the web root: ```bash sudo curl -sSo /var/www/html/test.css https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.css ``` Now request it without compression and print the number of bytes transferred. The `identity` encoding tells the server not to compress: ```bash curl -s -o /dev/null -H 'Accept-Encoding: identity' -w '%{size_download} bytes\n' http://localhost/test.css ``` ```output 281046 bytes ``` Keep this number: it is your baseline. The exact size can differ slightly between versions of the file. ## Step 2 - Configuring gzip in Nginx Skip to Step 5 if you use Apache. Ubuntu's Nginx package already sets `gzip on;` in `/etc/nginx/nginx.conf`, but only for `text/html`, and the tuning directives are commented out. Declaring `gzip on;` again in another file would make Nginx fail with a "directive is duplicate" error, so edit the existing block instead: ```bash sudo nano /etc/nginx/nginx.conf ``` Find the `Gzip Settings` section inside the `http` block and replace it with: ```nginx ## # Gzip Settings ## 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/xml application/rss+xml application/atom+xml application/manifest+json image/svg+xml font/ttf font/otf; ``` What each directive does: - `gzip_vary on` adds `Vary: Accept-Encoding`, so caches and CDNs store compressed and uncompressed copies separately. - `gzip_proxied any` also compresses responses to requests that came through a proxy or CDN. - `gzip_comp_level 5` is a good balance. Levels above 6 cost noticeably more CPU for a very small gain. - `gzip_min_length 256` skips tiny responses, where compression overhead is larger than the saving. - `gzip_types` lists the MIME types to compress. `text/html` is always compressed and must not be listed, otherwise Nginx logs a duplicate MIME type warning. Do not add images such as JPEG, PNG or WebP, or WOFF2 fonts: they are already compressed. Test the configuration and reload Nginx: ```bash sudo nginx -t && sudo systemctl reload nginx ``` ```output nginx: the configuration file /etc/nginx/nginx.conf syntax is ok nginx: configuration file /etc/nginx/nginx.conf test is successful ``` Check the headers of a gzip request. The command downloads the body to `/dev/null` and prints only the response headers: ```bash curl -s -o /dev/null -D - -H 'Accept-Encoding: gzip' http://localhost/test.css | grep -iE 'content-encoding|vary' ``` ```output Vary: Accept-Encoding Content-Encoding: gzip ``` ## Step 3 - Adding Brotli to Nginx Ubuntu 24.04 ships the Brotli module for Nginx as two packages in the `universe` repository: the filter module (compresses on the fly) and the static module (serves precompressed `.br` files). Install both, plus the `brotli` command line tool you will use in Step 4: ```bash sudo apt update sudo apt install libnginx-mod-http-brotli-filter libnginx-mod-http-brotli-static brotli ``` The packages drop `load_module` lines into `/etc/nginx/modules-enabled/`, so you do not need to load them by hand. Confirm they are there: ```bash grep -h brotli /etc/nginx/modules-enabled/*.conf ``` ```output load_module modules/ngx_http_brotli_filter_module.so; load_module modules/ngx_http_brotli_static_module.so; ``` Create a configuration file for Brotli. Files in `/etc/nginx/conf.d/` are included inside the `http` block: ```bash sudo nano /etc/nginx/conf.d/brotli.conf ``` ```nginx 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/xml application/rss+xml application/atom+xml application/manifest+json image/svg+xml font/ttf font/otf; ``` Brotli level 11 is meant for offline precompression, not for every request. Level 4 to 6 on the fly gives better compression than gzip at a comparable CPU cost. As with gzip, `text/html` is compressed by default and is not listed. Reload Nginx and request the file with Brotli: ```bash sudo nginx -t && sudo systemctl reload nginx curl -s -o /dev/null -D - -H 'Accept-Encoding: gzip, deflate, br' http://localhost/test.css | grep -i content-encoding ``` ```output Content-Encoding: br ``` When a client accepts both, Nginx prefers Brotli. Clients that only accept gzip still get gzip. ## Step 4 - Serving precompressed files with Nginx (optional) Compressing the same static file on every request wastes CPU. For assets that only change on deploy (CSS, JavaScript, SVG, fonts), you can compress them once at maximum level and let Nginx serve the ready-made `.gz` and `.br` files. The `gzip_static` module is built into Ubuntu's Nginx, and `brotli_static` comes from the package you installed in Step 3. Generate the compressed copies next to the originals. `gzip -k` and `brotli` both keep the original file: ```bash sudo find /var/www/html -type f \( -name '*.css' -o -name '*.js' -o -name '*.svg' -o -name '*.json' \) \ -exec gzip -k -9 -f {} \; -exec brotli -q 11 -f {} \; ``` ```bash ls -l /var/www/html/test.css* ``` ```output -rw-r--r-- 1 root root 281046 Sep 25 10:12 /var/www/html/test.css -rw-r--r-- 1 root root 26840 Sep 25 10:15 /var/www/html/test.css.br -rw-r--r-- 1 root root 35902 Sep 25 10:15 /var/www/html/test.css.gz ``` Enable static serving. Add these two lines to the end of `/etc/nginx/conf.d/brotli.conf`: ```bash sudo nano /etc/nginx/conf.d/brotli.conf ``` ```nginx gzip_static on; brotli_static on; ``` Reload Nginx: ```bash sudo nginx -t && sudo systemctl reload nginx ``` Nginx now looks for `test.css.br` or `test.css.gz` before compressing on the fly, and falls back to dynamic compression when no precompressed file exists. Remember to regenerate the `.gz` and `.br` files every time you deploy new assets, otherwise visitors will receive stale content. Run the `find` command from your deploy script or build pipeline. Continue with Step 7 to measure the result. ## Step 5 - Configuring gzip in Apache Apache on Ubuntu enables `mod_deflate` by default, and `/etc/apache2/mods-available/deflate.conf` already compresses HTML, plain text, XML, CSS and JavaScript. Confirm the module is loaded: ```bash sudo apache2ctl -M | grep -E 'deflate|brotli|filter' ``` ```output deflate_module (shared) filter_module (shared) ``` To also cover JSON, SVG and fonts and set a sensible compression level, replace the contents of the stock file: ```bash sudo nano /etc/apache2/mods-available/deflate.conf ``` ```apache AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css text/javascript AddOutputFilterByType DEFLATE application/javascript application/json application/xml AddOutputFilterByType DEFLATE application/rss+xml application/atom+xml application/manifest+json AddOutputFilterByType DEFLATE image/svg+xml font/ttf font/otf DeflateCompressionLevel 5 ``` `mod_deflate` adds the `Vary: Accept-Encoding` header by itself and skips responses that already carry a `Content-Encoding`, so you do not need the old browser-sniffing rules found in many copied configurations. Test and reload: ```bash sudo apache2ctl configtest && sudo systemctl reload apache2 ``` ```output Syntax OK ``` ```bash curl -s -o /dev/null -D - -H 'Accept-Encoding: gzip' http://localhost/test.css | grep -iE 'content-encoding|vary' ``` ```output Vary: Accept-Encoding Content-Encoding: gzip ``` ## Step 6 - Adding Brotli to Apache `mod_brotli` is part of the `apache2` package on Ubuntu, so there is nothing to compile. You only need to enable it and tell Apache which types to compress. Create the module's configuration file. Apache includes the files in `mods-enabled/` in alphabetical order, so `brotli.conf` is read before `deflate.conf`. That order matters: whichever filter runs first compresses the response, and the other one skips it. Putting Brotli first means clients that accept both get Brotli. ```bash sudo nano /etc/apache2/mods-available/brotli.conf ``` ```apache AddOutputFilterByType BROTLI_COMPRESS text/html text/plain text/xml text/css text/javascript AddOutputFilterByType BROTLI_COMPRESS application/javascript application/json application/xml AddOutputFilterByType BROTLI_COMPRESS application/rss+xml application/atom+xml application/manifest+json AddOutputFilterByType BROTLI_COMPRESS image/svg+xml font/ttf font/otf BrotliCompressionQuality 5 ``` Enable the module and restart Apache so the new module is loaded: ```bash sudo a2enmod brotli sudo apache2ctl configtest && sudo systemctl restart apache2 ``` Request the file advertising both encodings, like a browser does: ```bash curl -s -o /dev/null -D - -H 'Accept-Encoding: gzip, deflate, br' http://localhost/test.css | grep -i content-encoding ``` ```output Content-Encoding: br ``` If you get `gzip` here, the Brotli filter is running after `DEFLATE`. Check that the Brotli directives live in `mods-available/brotli.conf` and not in a file under `conf-enabled/`, which is loaded after the modules. ## Step 7 - Measuring the result Repeat the measurement from Step 1 with each encoding. This works the same for Nginx and Apache: ```bash for enc in identity gzip br; do printf '%-9s' "$enc" curl -s -o /dev/null -H "Accept-Encoding: $enc" -w '%{size_download} bytes\n' http://localhost/test.css done ``` ```output identity 281046 bytes gzip 36511 bytes br 29470 bytes ``` In this example gzip cuts the stylesheet by about 87% and Brotli saves a further 19% over gzip. With precompressed files from Step 4 the Brotli number drops a little more because level 11 was used. Finally, test from a browser against your HTTPS site: open the developer tools, go to the **Network** tab, reload, and check the `content-encoding` response header of your CSS and JavaScript files. You can also check from any machine with: ```bash curl -s -o /dev/null -D - -H 'Accept-Encoding: gzip, deflate, br' https://your_domain/ | grep -i content-encoding ``` ## Troubleshooting - **No `Content-Encoding` header at all.** The response type is not in your type list, or the response is smaller than the minimum length. Check the real type with `curl -sI http://localhost/file | grep -i content-type` and add it to `gzip_types` / `brotli_types` or `AddOutputFilterByType`. - **Responses from a backend (PHP-FPM, Node.js, a proxy) are not compressed in Nginx.** Make sure `gzip_proxied any;` is set, and that the backend does not already send `Content-Encoding`. Compress in one place only. - **Browsers never get Brotli, but `curl` does.** Browsers only request `br` over HTTPS. Test against `https://your_domain`. - **A CDN in front of the server.** The CDN may recompress or strip encodings. Keep `Vary: Accept-Encoding` enabled and check the CDN's own compression settings. - **High CPU after enabling compression.** Lower `gzip_comp_level` / `brotli_comp_level` (or `BrotliCompressionQuality`) to 4, and precompress static assets as in Step 4. ## Conclusion Your web server now compresses text responses with Brotli for modern browsers and falls back to gzip for everything else, and you have measured the size reduction with `curl`. Next, consider adding long `Cache-Control` headers for static assets so compressed files are also cached by browsers, enabling HTTP/2 or HTTP/3 on your HTTPS virtual host, and benchmarking the server under load with tools such as `ab` or `wrk` to confirm the CPU cost of compression is acceptable.