yum upgrades for production use, this is the repository for you.
Active subscription is required.
Technical Briefing: Enabling HTTP/3 in NGINX on Ubuntu and Debian
The Problem
Stock binaries can’t do HTTP/3
Stock nginx from the Debian and Ubuntu archives is built without --with-http_v3_module. The ondrej/nginx PPA also lacks it on current LTS releases. If your binary was built without the module, no configuration will enable HTTP/3.
Check with:
nginx -V 2>&1 | grep http_v3
If this returns nothing, setting http3 on; will either:
– throw unknown directive "http3", or
– leave Chrome negotiating HTTP/2.
A reload bug silently drops QUIC connections
Upstream NGINX 1.25.0 and newer silently drops about half of new QUIC connections after nginx -s reload when quic_bpf is on.
- Exiting workers leak reuseport sockets.
- Those leaked sockets intercept and drop new QUIC Initial packets.
- According to the article, F5 has not shipped the fix for over a year.
You don’t need a newer OpenSSL
A common belief is that HTTP/3 requires OpenSSL 3.2+ or quictls. The article calls this a myth. NGINX 1.25.2+ includes an OpenSSL QUIC compatibility layer that works on plain OpenSSL 1.1.1+ using TLS custom extensions.
The configure step probes three tiers in order:
1. OpenSSL 3.5.1+ native QUIC
2. BoringSSL, quictls or LibreSSL
3. The built-in compat layer
The Implementation: A Drop-In nginx Package from GetPageSpeed
The package provides:
- HTTP/3 on every supported release. It is compiled with
--with-http_v3_moduleon Ubuntu 20.04 focal, 22.04 jammy, 24.04 noble, Debian 12 bookworm and Debian 13 trixie. - The reload fix. It carries
quic_bpf_reload_fix.patch, which closes reuseport sockets during graceful shutdown. Exiting workers leave the kernel reuseport group, so new QUIC Initial packets never reach a dying worker. - A persistent QUIC host key. The postinst script generates
/etc/nginx/quic.keyon first install. It contains 32 random bytes from/dev/urandom, with permissions0640 root:www-data. - A firewall profile. A UFW profile named
Nginx QUICis installed at/etc/ufw/applications.d/nginx. - Drop-in replacement. It declares Breaks/Replaces on
nginx-core,nginx-full,nginx-common,nginx-extrasandnginx-light, so migration is a singleapt install nginx.
Support matrix
| Release | System OpenSSL | HTTP/3 support |
|---|---|---|
| focal | 1.1.1 | compat layer |
| jammy | 3.0.2 | compat layer |
| noble | 3.0.13 | compat layer |
| bookworm | 3.0.x | compat layer |
| trixie | 3.5.x | native QUIC API |
Installation
Run these as root or via sudo. The steps are identical on all five codenames.
1. Add the keyring:
sudo install -d -m 0755 /etc/apt/keyrings
curl -fsSL https://extras.getpagespeed.com/deb-archive-keyring.gpg | sudo tee /etc/apt/keyrings/getpagespeed.gpg >/dev/null
2. Add the APT source:
distro=$(lsb_release -is | tr '[:upper:]' '[:lower:]')
codename=$(lsb_release -cs)
echo "deb [signed-by=/etc/apt/keyrings/getpagespeed.gpg] https://extras.getpagespeed.com/${distro} ${codename} main" | sudo tee /etc/apt/sources.list.d/getpagespeed-extras.list
For mainline, append a second line using ${codename}-mainline main with tee -a.
3. Pin the package. This step is critical. Without the pin, apt may prefer a higher-versioned stock nginx. Write /etc/apt/preferences.d/getpagespeed-nginx.pref with:
Package: nginx nginx-common nginx-core nginx-full nginx-extras nginx-light nginx-module-* libnginx-mod-*
Pin: origin extras.getpagespeed.com
Pin-Priority: 1001
4. Install:
sudo apt-get update
sudo apt-get install nginx
Verify the install
Check for the HTTP/3 module:
nginx -V 2>&1 | tr ' ' 'n' | grep http_v3
It should print --with-http_v3_module. The 'n' is reproduced as printed in the article and is almost certainly meant to be '\n'.
Check the package origin and version:
dpkg -s nginx | grep -E 'Maintainer|Version'
- Expect
Maintainer: GetPageSpeed LLC <builder@getpagespeed.com>. - Expect a version like
1:1.28.3-11~gps1+ubuntu2404+stable. Other suffixes are+ubuntu2204+stable,+ubuntu2004+stable,+deb12+stableand+deb13+stable.
If the module is missing, the pin isn’t working. Check with apt-cache policy nginx.
Configuration
Minimal server block
For example, in /etc/nginx/conf.d/h3-example.conf, include these directives:
listen 443 ssl;andlisten 443 quic reuseport;, plus the[::]:443equivalentsssl_protocols TLSv1.3;(QUIC requires TLS 1.3)http2 on;andhttp3 on;add_header Alt-Svc 'h3=":443"; ma=86400';
Apply with:
sudo nginx -t && sudo systemctl reload nginx
Multiple virtual hosts
listen 443 quic reuseportmust appear exactly once per address family. Put it on the first ordefault_serverblock.- Use plain
listen 443 quic;everywhere else.
UDP buffers
In /etc/sysctl.d/99-quic.conf, set each of these to 7500000:
– net.core.rmem_max
– wmem_max
– rmem_default
– wmem_default
Apply with:
sudo sysctl -p /etc/sysctl.d/99-quic.conf
Kernel offload and host key
| Directive | Context | Purpose | Requirement |
|---|---|---|---|
quic_bpf on; |
main, before events {} |
Steers packets to the correct worker by Destination Connection ID | Kernel 5.7+ and CAP_SYS_ADMIN on the master process. All supported releases’ stock kernels meet both. |
quic_gso on; |
http | Enables UDP segmentation offload | Kernel 4.18+ |
quic_host_key /etc/nginx/quic.key; |
http | Keeps address validation tokens valid across restarts | Without it, every restart invalidates the tokens and forces clients through an extra round-trip |
Firewall
Open the ports with the bundled profile:
sudo ufw allow 'Nginx QUIC'
sudo ufw reload
This opens TCP 80, TCP 443 and UDP 443.
Alternatively:
sudo ufw allow 443/tcp
sudo ufw allow 443/udp
UDP 443 is the rule most often forgotten. Without it, browsers never get past h2.
DNS HTTPS records
HTTPS records (type 65, RFC 9460) let browsers skip the Alt-Svc bootstrap.
Add:
example.com. 300 IN HTTPS 1 . alpn="h3,h2"
Verify with:
dig example.com HTTPS +short
Cloudflare synthesizes these records automatically when proxying (orange cloud) is enabled.
Practical Payoff: Reload Fix Test Results
- Stock nginx with
quic_bpf on: reloads drop roughly half of new connections for several seconds. - Patched package on noble (OpenSSL 3.0.13, compat layer): 298 consecutive
curl --http3requests were sent during three back-to-backnginx -s reloadcalls. All 298 returned HTTP/3 200, with zero drops.
Verification
curl
Only Debian 13’s stock curl (8.14) has HTTP/3. Ubuntu 20.04–24.04 and Debian 12 ship curl builds without it.
Check your curl build:
curl -V | grep -o HTTP3
Test the server:
curl --http3 -sv https://example.com/ -o /dev/null -w 'protocol=%{http_version} code=%{http_code}n'
Expect protocol=3 code=200. As in the module check above, the trailing n likely stands for \n.
Other checks
- Browser DevTools: Enable the Protocol column and load the site twice. The first request bootstraps over h2; the second should show h3.
- External check: http3check.net, which also checks for an HTTPS DNS record.
- Logging: Add
"$server_protocol"tolog_format. HTTP/3 requests are logged asHTTP/3.0.
Troubleshooting
Drops still occur after reload
– Confirm that dpkg -s nginx | grep Maintainer shows GetPageSpeed LLC.
– If it does and drops continue, file an issue.
No --with-http_v3_module, or unknown directive "http3"
– The pin isn’t in effect.
– In apt-cache policy nginx, the extras.getpagespeed.com line should show priority 1001.
– Fix the .pref file, then run sudo apt-get install --reinstall nginx.
Browser still shows HTTP/2
– The cause is a missing HTTPS DNS record, a missing Alt-Svc header, or a cold bootstrap cache.
– Check the header with curl -sI https://example.com/ | grep -i alt-svc.
bind() failed (13: Permission denied) on UDP 443
– This is usually AppArmor.
– Check with journalctl -k | grep -i apparmor.
– Adjust the profile, or temporarily run sudo aa-complain /etc/apparmor.d/usr.sbin.nginx.
UDP buffer errors in journalctl -u nginx
– Re-run sudo sysctl -p /etc/sysctl.d/99-quic.conf.
failed to create BPF map (1: Operation not permitted) or ngx_quic_bpf_module failed to initialize
– Possible causes:
– the kernel is older than 5.7,
– the master process lacks CAP_SYS_ADMIN, or
– nginx is running in an unprivileged container.
– Fixes: drop quic_bpf on;, upgrade the kernel, or grant the capability.
Key Limitation: No 0-RTT on Most Releases
What’s missing. The compat layer cannot do 0-RTT resumption. ssl_early_data on; needs OpenSSL 3.5.1+ or a QUIC-capable backend such as quictls.
Where it works. On Ubuntu 20.04/22.04/24.04 and Debian 12, ssl_early_data has no effect. The article stresses this is not a bug. Debian 13 trixie supports 0-RTT with the stock package.
Planned fix.
– The same repository already ships quictls, quictls-libs, quictls-dev and quictls-static. This is a co-installable OpenSSL 3.1.x fork installed at /usr/lib/<multiarch>/quictls/, which leaves the system libssl3 untouched.
– A quictls-linked nginx rebuild for Ubuntu releases is on the roadmap.
– The maintainers say user demand determines priority. Email info@getpagespeed.com and name your codename.
Side Note
The deprecated nginx-amplify-agent reaches EOL in January 2026. The same vendor offers a compatible replacement that runs scheduled gixy config scans.
Read the full article: How to Enable NGINX HTTP/3 on Ubuntu and Debian
