---
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.