Matino Matino
  • Servers
    • Proxmox
  • Web Dev
  • Gaming
    • PC Gaming
  • Guides
  • Security

Language

  • English English ✓

Only reviewed, locally published translations can be opened.

Publication gate closed

Translation unavailable

This language will open after its translation is reviewed and published locally.

Font ResizerAa

Notifications

No new notifications. Follow a category to prepare local update alerts.

Manage notifications

My Matino

Personalization Your data Sign in
Joseph MatinoJoseph Matino
Servers Web Dev Gaming

Language

  • English English ✓

Only reviewed, locally published translations can be opened.

Publication gate closed

Translation unavailable

This language will open after its translation is reviewed and published locally.

Font ResizerAa

Notifications

No new notifications. Follow a category to prepare local update alerts.

Manage notifications

My Matino

Personalization Your data Sign in
Find a guide
Search Matino
  • Servers
    • Proxmox
  • Web Dev
  • Gaming
    • PC Gaming
  • Guides
  • Security
Servers Web Dev Gaming Guides Security
  • English English ✓

Only reviewed, locally published translations can be opened.

Publication gate closed

Translation unavailable

This language will open after its translation is reviewed and published locally.

Follow US
  • Terms & Conditions
  • Privacy Policy
  • Contact
© Joseph Matino. All Rights Reserved.

How to Build and Enable Brotli for Nginx on Ubuntu 22.04

Joseph Matino
Last updated: July 27, 2026 6:00 am
By Joseph Matino
Step-by-step guide to install Brotli nginx compression on Ubuntu 22.04. Includes compilation, HestiaCP configuration, and troubleshooting tips.
SHARE
+14Spark this articlePreferences consent remembers one private reader identity, so your spark stays reversible.

For a long time, I treated compression as something the edge would handle for me. Cloudflare made that easy, and for many sites that is enough. The moment I started tightening my own Nginx and Hestia servers, I wanted the origin itself to prove smaller text responses, gzip fallback still alive, cache behavior unchanged, and WordPress assets still loading cleanly.

My test lane uses Ubuntu 22.04, Nginx, and the HestiaCP layout where global Nginx config and per-domain includes are separate. Newer Ubuntu releases provide Brotli module packages for the Ubuntu-packaged Nginx, so check the package path before compiling. The source-build path below remains useful on Ubuntu 22.04 and on custom or control-panel Nginx builds that need a module compiled against the exact running binary.

Article ContentsTap to jump
01What Brotli changes compared with gzip02What you need before touching Nginx03Step 1: Check the exact Nginx version04Step 2: Install the build tools05Step 3: Download and build the Brotli module06Step 4: Copy the compiled modules07Step 5: Load the Brotli modules in Nginx08Step 6: Add the HestiaCP site config09Final step: prove Brotli is actually serving10Cache and CDN notes11Troubleshoot module and response-header failures12What to prove before you call Brotli finished
Section 1 of 12

The Nginx path usually needs dynamic Brotli modules from ngx_brotli. The safe path is to build those modules against the exact Nginx version running on the server, load them, run nginx -t, then test with curl before touching cache or CDN settings. By the end, the server should prove that the modules load, the config test passes, and a request with Accept-Encoding: br returns content-encoding: br while gzip remains available for fallback.

What Brotli changes compared with gzip

Most web servers still use gzip compression because it is stable, fast, and supported everywhere. Brotli is the better test when a page sends large text assets, but I do not treat it as automatically better for every response. Compare the same HTML, CSS, and JavaScript files with gzip and Brotli, then trust the measured transfer size.

Text-heavy assets show the difference most clearly. I test theme CSS, plugin JavaScript, SVG, JSON, and long HTML responses. Images are not the target here because modern image formats already have their own compression. That is why I use real CSS, JavaScript, and HTML URLs instead of judging from the homepage alone.

One reason I like Brotli is the history behind it. Google introduced Brotli as an open-source general-purpose compressor, and the original Open Source announcement focused on smaller transfers for real users, including mobile users. The later Google Research paper describes Brotli as a general-purpose compressor designed for Internet content. I treat it as a server feature that must prove its value through headers and byte size, not as an SEO trick.

Checks that prove whether Brotli helps

After enabling Brotli, I check the response headers, the transferred bytes for the same URL with gzip and Brotli, and whether any cache layer changes the result. Smaller transferred bytes can help page speed, especially on slower networks, but Brotli will not fix render-blocking scripts, unused CSS, oversized images, or bloated theme output.

The first number I look at is transfer size, not a synthetic score. If Brotli saves a meaningful amount on the assets visitors actually download, it can help slower routes, mobile users, and repeated page views. If the file is already tiny or already well-compressed, the gain may be small, and that is fine too.

Checks that verify the Nginx and Brotli configuration
Proof Command or check Expected proof
Nginx version nginx -v The exact version used for the module build
Module path nginx -V 2>&1 | grep modules-path The directory where the compiled modules belong
Config safety nginx -t A clean test before any reload
Brotli response curl -H "Accept-Encoding: br" -I URL content-encoding: br
Fallback response curl -H "Accept-Encoding: gzip" -I URL content-encoding: gzip when Brotli is not requested
Transfer size Compare the same URL with gzip and Brotli Measured bytes, not a guessed percentage

What you need before touching Nginx

Do this on a server where you are comfortable rebuilding an Nginx module and testing the config before reload. A small VPS can handle it, but the build step needs enough memory and temporary disk space to compile cleanly. If the server is already under pressure, take a backup or snapshot first and do the build during a quiet window.

You also need root access or a user with sudo privileges. The work touches system packages, Nginx module files, and Nginx configuration. If you are using HestiaCP, do not paste random config into every site file first. Get the module loaded globally, test Nginx, then decide where the per-site Brotli settings belong.

On a clean Ubuntu server, missing build packages are the most common early problem. That is normal. Install the compiler and development libraries first, then keep the terminal output visible until the module build finishes. If a package is missing, the error usually points close to the missing dependency.

A percentage from another server cannot tell me what my origin sends. I compare one HTML page, one CSS file, and one JavaScript file from the server I am changing. That small sample exposes whether Brotli changes the real transfer sizes before I spend time on broader tuning.

The most important detail is the exact Nginx version. Build the dynamic Brotli module against the same Nginx version that is running on the server. If those do not match, Nginx can refuse to load the module or fail its config test.

Step 1: Check the exact Nginx version

Start with the Nginx binary already serving the site. The version returned here decides which Nginx source archive you download later.

Bash
nginx -v

Example output shape. Your installed version will differ.

Bash
nginx version: nginx/1.29.0

Write down your own version number. Next, check where Nginx expects to find dynamic modules:

Bash
nginx -V 2>&1 | grep modules-path

Example output shape. Use the path returned by your own binary.

Bash
--modules-path=/usr/lib/nginx/modules

That path is where the compiled Brotli module files need to land on your server.

Step 2: Install the build tools

If the server uses Ubuntu-packaged Nginx on Ubuntu 24.04 or later, check the distribution modules first. The packages libnginx-mod-http-brotli-filter and libnginx-mod-http-brotli-static can avoid a source build when their Nginx package matches the installed binary. Do not mix those modules into a custom Hestia or third-party Nginx build without proving binary compatibility.

For the Ubuntu 22.04 or custom-binary path in this guide, update the package list so Ubuntu installs current package metadata.

Bash
sudo apt update

Then install the compiler, Git, CMake, and the development libraries Nginx needs for the module build:

Bash
sudo apt install build-essential git gcc cmake libpcre3 libpcre3-dev zlib1g zlib1g-dev openssl libssl-dev

On most clean Ubuntu 22.04 servers, those packages are enough to build the dynamic modules. If your server already has some of them, apt will skip what is installed. If apt reports a missing package, stop and fix that package issue before moving to the source download.

Step 3: Download and build the Brotli module

Move to a temporary directory for the build work. The commands below use $NGINX_VERSION, so set it from your live Nginx output first with NGINX_VERSION="$(nginx -v 2>&1 | cut -d/ -f2)".

Bash
cd /tmp

Download the Nginx source archive for that version. The source version must match the running Nginx version, because you are building a dynamic module for that binary.

Bash
wget https://nginx.org/download/nginx-$NGINX_VERSION.tar.gz
tar -xzf nginx-$NGINX_VERSION.tar.gz

Then download the Brotli module source with its submodules:

Bash
git clone --recursive https://github.com/google/ngx_brotli.git

Enter the Nginx source directory:

Bash
cd nginx-$NGINX_VERSION

Configure the build to create dynamic modules without replacing your installed Nginx binary. This is the part I do not skip: the ngx_brotli README says dynamic modules must use the same configure arguments as the installed Nginx build, then append --with-compat and the Brotli dynamic module path. If you ignore that, Nginx can reject the module as not binary compatible.

Bash
NGINX_CONFIGURE_ARGS="$(nginx -V 2>&1 | sed -n 's/^configure arguments: //p')"
./configure $NGINX_CONFIGURE_ARGS --with-compat --add-dynamic-module=../ngx_brotli

Compile only the module files:

Bash
make modules

Prove the two build artifacts instead of matching a compiler transcript.

Bash
ls -lh objs/ngx_http_brotli_filter_module.so 
       objs/ngx_http_brotli_static_module.so

The build may take a few minutes. If it fails with missing library errors, do not force the next step. Install the missing development package, rerun the configure step, and build again.

If the Brotli repository did not pull its submodules cleanly, refresh them before building again:

Bash
cd ../ngx_brotli
git submodule update --init
cd ../nginx-$NGINX_VERSION

Step 4: Copy the compiled modules

Create the modules directory if it does not exist:

Bash
sudo mkdir -p /usr/lib/nginx/modules

Copy the two compiled module files into the Nginx modules directory, then confirm they are really there:

Bash
sudo cp objs/ngx_http_brotli_filter_module.so /usr/lib/nginx/modules/
sudo cp objs/ngx_http_brotli_static_module.so /usr/lib/nginx/modules/
sudo chmod 644 /usr/lib/nginx/modules/ngx_http_brotli_*.so
ls -la /usr/lib/nginx/modules/ngx_http_brotli_*.so

If both files are listed, the build produced the modules and Nginx can load them from the expected directory.

Step 5: Load the Brotli modules in Nginx

Open the main Nginx configuration file to load the dynamic modules. This part belongs in the main context, before the events and http blocks.

Bash
sudo nano /etc/nginx/nginx.conf

Add the module load lines near the top of the file:

Bash
# Brotli modules
load_module /usr/lib/nginx/modules/ngx_http_brotli_filter_module.so;
load_module /usr/lib/nginx/modules/ngx_http_brotli_static_module.so;

If you are not using HestiaCP, you can enable Brotli in the http block or in the site config. Do not remove gzip yet. Keep gzip available as the fallback path while you prove Brotli works.

Bash
# Brotli compression
brotli on;
brotli_comp_level 6;
brotli_static on;
brotli_min_length 1024;
brotli_types
    text/plain
    text/css
    text/xml
    text/javascript
    application/javascript
    application/xml+rss
    application/json
    image/svg+xml
    font/ttf
    font/otf
    font/woff
    font/woff2;

add_header Vary Accept-Encoding;

Test the configuration first. Reload Nginx only if the test passes:

Bash
sudo nginx -t
sudo systemctl reload nginx

Step 6: Add the HestiaCP site config

For HestiaCP, keep the module load lines in the main Nginx config, then put the site-level Brotli directives in a custom include for the domain. Hestia can regenerate normal domain config files, so do not treat a generated domain file as the permanent owner. Start in the domain config directory:

Bash
cd /home/username/conf/web/example.com/

For the HTTPS site, create or edit the SSL custom include file. If you intentionally serve plain HTTP too, mirror the needed setting in an HTTP custom include as well.

Bash
sudo nano nginx.ssl.conf_custom

Add the Brotli site directives there:

Bash
# Enable brotli compression for this site
brotli on;
brotli_comp_level 6;
brotli_static on;
brotli_min_length 1024;
brotli_types
    text/plain
    text/css
    text/xml
    text/javascript
    application/javascript
    application/xml+rss
    application/json
    image/svg+xml
    font/ttf
    font/otf
    font/woff
    font/woff2;

add_header Vary Accept-Encoding;

The ownership split is straightforward. Main Nginx loads the modules, and the Hestia domain include enables compression for the site. Test the complete configuration before reloading Nginx.

Bash
sudo nginx -t
sudo systemctl reload nginx

Final step: prove Brotli is actually serving

Test the HTML response first. Replace example.com with your own domain:

Bash
curl -H "Accept-Encoding: br" -I https://example.com/

Example response shape. Verify these headers on your own URL.

Bash
HTTP/2 200
server: nginx
content-encoding: br
vary: Accept-Encoding

The key line is content-encoding: br. If it is missing, Brotli is not serving that response yet. Do not move to cache tuning until this header is true on the exact URL you are testing.

Then test a real CSS or JavaScript file that is larger than your brotli_min_length value:

Bash
curl -H "Accept-Encoding: br" -I https://example.com/wp-content/themes/yourtheme/style.css

Example asset response shape. Verify it on the file you requested.

Bash
HTTP/2 200
content-type: text/css
content-encoding: br
vary: Accept-Encoding

Cache and CDN notes

Do not start with the CDN. Prove Brotli at the origin first, then test any cache or CDN layer separately. If you skip that order, you will not know whether the origin is wrong or the edge cache is hiding the real response.

If a CDN or reverse proxy is in front of Nginx, test both layers. First request the origin directly, then request the public URL through the edge. Compare content-encoding, vary, cache status headers, and transferred bytes.

If the edge layer compresses responses itself, avoid double-guessing the result. Pick one owner for the final public response, clear the cache after config changes, and test again with the same URL and the same Accept-Encoding header.

Troubleshoot module and response-header failures

Most failures come from a missing build dependency, a module in the wrong directory, or a version mismatch between Nginx and the compiled module.

When Nginx fails to start after adding the Brotli modules, check that the module files exist in the directory reported by nginx -V. Running ls -la /usr/lib/nginx/modules/ngx_http_brotli* should show both files when that is the configured module path. If they are missing, return to the build artifacts instead of reloading Nginx again.

If Nginx configuration tests fail after adding Brotli directives, verify that the load_module statements are in the main Nginx context, above the http block. Brotli directives such as brotli, brotli_static, brotli_types, and brotli_comp_level can be applied in http, server, or location context depending on whether you want global or site-level behavior.

When compression headers do not appear in HTTP responses, check several potential causes. First, verify that your test requests include the correct Accept-Encoding header. The file being tested must be larger than the minimum compression size (usually 1024 bytes) and must be listed in the brotli_types configuration.

What to prove before you call Brotli finished

The final check is not a score. It is a byte and header check against the real URL you care about. Test the uncompressed response, gzip, and Brotli with the same path so you can see whether Nginx is changing the transfer size and whether the expected encoding is returned.

Bash
curl -H "Accept-Encoding: identity" -o /dev/null -w 'identity code=%{http_code} bytes=%{size_download}
' https://example.com/
curl -H "Accept-Encoding: gzip" -o /dev/null -w 'gzip code=%{http_code} bytes=%{size_download}
' https://example.com/
curl -H "Accept-Encoding: br" -o /dev/null -w 'brotli code=%{http_code} bytes=%{size_download}
' https://example.com/

If the Brotli request does not return content-encoding: br, the feature is not live for that response yet. On a recent Hestia and Nginx audit, I saw gzip configured and --with-compat available, but no Brotli module files loaded. That kind of check is useful because it stops you from writing a success story before the server proves it.

For Hestia servers, keep a small note of the running Nginx version, the module path from nginx -V, the domain include file you changed, and the exact URL you tested. That makes future Nginx updates safer because you know what must be rebuilt or rechecked.

Was this guide useful?Send one spark to support practical, tested writing.
Share This Article
Facebook Whatsapp Whatsapp Reddit Copy Link
Joseph Matino
ByJoseph Matino
Follow:
Full-stack developer, quant systems builder, and server-performance engineer working across WordPress, Linux, Docker, private networks, automation, and trading-system research. Matino documents real builds from my own work, including what I tested, what broke, what improved, and what I would change before using the same idea again.
Previous Article Proxmox VE 9.0 brings Debian 13, better hardware support, and storage snapshots. Complete upgrade guide for bare metal servers. Proxmox VE 9.0: Better Bare Metal Performance with Debian 13
guest
guest
0 Comments
Most Voted
Newest Oldest

Latest Posts

View all
Step-by-step guide to install Brotli nginx compression on Ubuntu 22.04. Includes compilation, HestiaCP configuration, and troubleshooting tips.

How to Build and Enable Brotli for Nginx on Ubuntu 22.04

Servers
4 weeks ago
Proxmox VE 9.0 brings Debian 13, better hardware support, and storage snapshots. Complete upgrade guide for bare metal servers.

Proxmox VE 9.0: Better Bare Metal Performance with Debian 13

Proxmox
2 months ago
Best Black Myth Wukong Mods

Best Black Myth Wukong Mods in 2024

PC Gaming
2 months ago
Starlink Vs Safaricom: Could Kenya’s Internet Shift in 2024?

Starlink Vs Safaricom: Could Kenya’s Internet Shift in 2024?

Networking
2 months ago
Matino

I build and document production systems across WordPress, Proxmox and Linux infrastructure, cloud applications, private networks, developer tools, automation, and quantitative software.

EXPERTISE

Cloud Application Architect

Software, systems & infrastructure

WordPress Specialist

Performance, security & plugins

Quant Systems Builder

Algorithms, data & risk

Explore

Blog
Servers
Algorithms & Quant
WordPress
Web Development
Gaming
Guides

About

About Me
Contact
Privacy Policy
Terms of Use
Cookie Policy
Legal Notice
Disclaimer

Work with Joseph

Available for WordPress, Proxmox and Linux infrastructure, cloud applications, automation, and quantitative software.

Let's Build

Connect

Follow new engineering articles, developer tools, and build notes.

Github Facebook-f X-twitter Youtube Instagram Rss
WordPress

Built with

Linux

Engineered on

Docker

Containerized with

Best Practices

Secured by

Privacy First

Designed for

Linode

Hosted on

© 2026 Matino. All rights reserved.

Building in public. Sharing knowledge. Shipping systems.

Privacy Policy • Terms of Use • Cookie Policy • Legal Notice

  • Sitemap
  • RSS
  • Back to top

Your privacy

Choose how Matino uses cookies

Essential storage keeps the site working. Analytics, advertising, and external video stay off unless you allow them.

Privacy policy Cookie policy
wpDiscuz