Enable HTTP/3 and Brotli compression on Nginx

Why HTTP/3 and Brotli matter for your VPS
Users expect pages to load in under two seconds. On a Linux VPS, two optimizations that can dramatically reduce latency are HTTP/3 (the QUIC-based protocol) and Brotli compression. HTTP/3 eliminates TCP head-of-line blocking and reduces connection setup time compared to HTTP/2 and HTTP/1.1. Brotli, developed by Google, compresses text assets like HTML, CSS, and JavaScript 20-30% better than gzip at the same quality level.
Your VPS hardware already delivers the raw speed, the 4 vCPUs and NVMe storage can push a lot of data. But the protocol and compression layer can become a bottleneck. Enabling HTTP/3 and Brotli together reduces the number of round trips and the payload size, especially for API responses and mobile clients. On a modern Ubuntu 24.04 VPS with a dedicated IPv4, these features are production-ready and supported by every major browser (Chrome, Edge, Firefox since 2020, Safari since 2021).
This guide shows you how to compile Nginx from source with the ngx_brotli module, configure HTTP/3 (QUIC), and verify that both Brotli and HTTP/3 are working. We will also cover SSL termination with Let's Encrypt, HTTP/3 requires TLS 1.3.
Prerequisites
- A VPS running Ubuntu 24.04 LTS (the process is similar on Debian 12 or AlmaLinux 9, but package names may differ)
- Root access (
sudo) - A domain name (e.g.
example.com) whose DNS A record points to your VPS IP - Ports 443/UDP (QUIC) open in your firewall (UFW or nftables)
- Basic familiarity with Nginx configuration and systemd
Step 1 - Install build dependencies and download Nginx source
The Brotli module (ngx_brotli) is not included in the official Ubuntu Nginx package. We have two options: compile from source (preferred for full control) or use a community PPA (less control). We will compile from source, which gives us the latest features and lets us select exactly which modules to include.
Update your system and install the tools needed to compile Nginx:
sudo apt update
sudo apt upgrade -y
sudo apt install -y build-essential libpcre3-dev libssl-dev zlib1g-dev libxslt1-dev libgd-dev libgeoip-dev git wget
Download the latest stable Nginx source (verify the version at nginx.org; as of this writing 1.26.2 is the stable branch):
wget https://nginx.org/download/nginx-1.26.2.tar.gz
tar -xzf nginx-1.26.2.tar.gz
cd nginx-1.26.2
Download the ngx_brotli module source:
git clone --recurse-submodules https://github.com/google/ngx_brotli.git
This clones the main repository and the brotli C library submodule inside it.
Step 2 - Configure and compile Nginx with Brotli support
We need to tell the configure script to include the Brotli module and enable HTTP/2 (HTTP/3 in nginx 1.25+ uses the same --with-http_v3_module flag). The http_v3_module requires the OpenSSL library. We also include --with-http_ssl_module --with-stream --with-http_v2_module for a full-featured build.
./configure \
--prefix=/etc/nginx \
--sbin-path=/usr/sbin/nginx \
--modules-path=/usr/lib/nginx/modules \
--conf-path=/etc/nginx/nginx.conf \
--error-log-path=/var/log/nginx/error.log \
--http-log-path=/var/log/nginx/access.log \
--pid-path=/var/run/nginx.pid \
--lock-path=/var/lock/nginx.lock \
--with-http_ssl_module \
--with-http_v2_module \
--with-http_v3_module \
--with-stream \
--with-stream_ssl_module \
--add-module=./ngx_brotli
Now compile and install:
make -j$(nproc)
sudo make install
Verify the installation:
nginx -V 2>&1 | grep -o 'ngx_brotli\|http_v3_module'
Expected output, both modules appear:
ngx_brotli
http_v3_module
Step 3 - Create a systemd service for Nginx
By default, make install does not register Nginx as a systemd service. Create the service unit file:
sudo nano /etc/systemd/system/nginx.service
Paste the following:
[Unit]
Description=nginx - high performance web server
Documentation=https://nginx.org/en/docs/
After=network-online.target remote-fs.target nss-lookup.target
Wants=network-online.target
[Service]
Type=forking
PIDFile=/var/run/nginx.pid
ExecStartPre=/usr/sbin/nginx -t -q -g 'daemon on; master_process on;'
ExecStart=/usr/sbin/nginx -g 'daemon on; master_process on;'
ExecReload=/usr/sbin/nginx -g 'daemon on; master_process on;' -s reload
ExecStop=/bin/kill -s TERM $(cat /var/run/nginx.pid)
PrivateTmp=true
[Install]
WantedBy=multi-user.target
Reload systemd and enable Nginx:
sudo systemctl daemon-reload
sudo systemctl enable nginx
sudo systemctl start nginx
Verify:
sudo systemctl status nginx
Expected: active (running).
Step 4 - Configure SSL with Let's Encrypt
HTTP/3 requires TLS 1.3. Install certbot and obtain a certificate:
sudo apt install -y certbot
sudo certbot certonly --standalone -d example.com -d www.example.com
Replace example.com with your domain. After success, the certificates will be at /etc/letsencrypt/live/example.com/fullchain.pem and privkey.pem.
Step 5 - Configure Nginx for HTTP/3 and Brotli
Open the main config file:
sudo nano /etc/nginx/nginx.conf
Within the http block, enable Brotli globally. Add this inside http { ... }:
brotli on;
brotli_comp_level 6;
brotli_static on;
brotli_types text/plain text/css application/javascript application/json application/xml image/svg+xml;
Now configure your server block (create a new one in /etc/nginx/conf.d/example.com.conf if you prefer). Here is a minimal config that listens for HTTP/3 on UDP 443 and for HTTP/1.1/HTTP/2 on TCP 443:
server {
listen 443 quic reuseport; # HTTP/3
listen 443 ssl; # HTTP/1.1 + HTTP/2
server_name example.com www.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305;
# Enable Alt-Svc header to advertise HTTP/3
add_header Alt-Svc 'h3=":443"; ma=86400';
root /var/www/html;
index index.html index.htm;
location / {
try_files $uri $uri/ =404;
}
}
Breakdown of the key lines:
| Directive | Purpose |
|---|---|
listen 443 quic reuseport | Accepts QUIC (HTTP/3) connections on UDP 443. reuseport allows multiple worker processes to accept connections on the same socket, which is required for QUIC. |
listen 443 ssl | Normal TCP socket for HTTP/1.1 and HTTP/2. |
ssl_protocols TLSv1.3 | QUIC can only run over TLS 1.3. |
add_header Alt-Svc ... | Advertises to clients that this server supports HTTP/3 on port 443. Without this header, clients will not upgrade to HTTP/3. |
brotli ... | Enables Brotli compression at level 6 (good balance of speed and ratio). Also compresses pre-compressed .br files via brotli_static. |
Test the configuration:
sudo nginx -t
Output: syntax is ok + test is successful.
Reload Nginx:
sudo systemctl reload nginx
Step 6 - Configure the firewall for QUIC
If you are using UFW (Ubuntu's default firewall), allow QUIC:
sudo ufw allow 443/udp comment 'HTTP/3 QUIC'
sudo ufw reload
For nftables, add a rule like:
sudo nft add rule inet filter input udp dport 443 ct state new accept
Verify that the port is open from an external host:
sudo nmap -sU -p 443 example.com
Expected: 443/udp open|filtered (some networks filter UDP, but the port should respond).
Step 7 - Test the configuration from a browser
Open Chrome or Edge and navigate to https://example.com. Enable the Developer Tools (F12), go to the Network tab, reload the page. Right-click on the column headers and enable the Protocol column.
Expected result for a resource on your server:
- h3, HTTP/3 (QUIC)
- h2 or http/2+h2c for fallback
To verify Brotli, use cURL from a local machine:
curl -H "Accept-Encoding: br" -I https://example.com/
Expected in the response headers:
content-encoding: br
If you see gzip or no encoding, Brotli is not active. Check the brotli_types directive, ensure it includes the MIME type of the resource being tested (e.g., text/html).
Troubleshooting
Nginx fails to start with "unknown directive 'brotli'"
The Brotli module was not compiled or loaded. Run nginx -V and confirm ngx_brotli appears. If not, re-run ./configure with --add-module=./ngx_brotli and recompile.
Browsers show h2 instead of h3
Check the firewall: sudo ufw status must include 443/udp. Also verify the Alt-Svc header is present: curl -I https://example.com | grep -i alt-svc. If it is missing, add the add_header line to the server block.
Brotli compression not applied to CSS/JS
The brotli_types directive must list the exact MIME types. Common missing ones: application/javascript (not text/javascript), image/svg+xml, application/json. Restart Nginx after editing.
FAQ
Does HTTP/3 work without a valid SSL certificate?
No. HTTP/3 uses QUIC, which requires TLS 1.3. You must have a valid certificate from a trusted CA (Let's Encrypt works).
Can I use an older Nginx version?
Nginx 1.25+ supports the built-in http_v3_module. Earlier versions do not support QUIC. We recommend the latest stable: 1.26.x.
Is Brotli always better than gzip?
Brotli compresses text up to 26% more than gzip at level 6, but at the cost of higher CPU usage during compression. For dynamic content, gzip might be slightly faster. For static files, Brotli is a clear win. You can serve both via content negotiation (browsers accept both).
Does the Brotli module work with HTTP/3 specifically?
Yes, Brotli works at the HTTP/1.1 and HTTP/2 layers. HTTP/3 (QUIC) transports HTTP/2 frames, so Brotli compression applies normally. No special configuration is needed beyond enabling it in the http block.
Can I enable Brotli on a managed WordPress VPS?
If you are using an WordPress VPS with LiteSpeed, LiteSpeed includes built-in Brotli support. For an Nginx-based WordPress setup, the compile-from-source method in this guide works perfectly.
Related articles
- Reverse proxy with automatic HTTPS using Caddy
- Security audit and hardening with Lynis on a VPS
- Self-hosting n8n for workflow automation on a VPS
- Hardening SSH on a new VPS: keys, ports, fail2ban, and CrowdSec


