Site icon GetPageSpeed

How to Enable NGINX HTTP/3 on Ubuntu and Debian

How to Enable NGINX HTTP/3 on Ubuntu and Debian

Getting NGINX HTTP/3 on Ubuntu to actually work is harder than the nginx docs make it look. You enable http3 on; in your NGINX config, restart, and Chrome still negotiates HTTP/2. Or worse: HTTP/3 appears to work, but connections silently die after every nginx -s reload. Both problems trace back to how stock Debian/Ubuntu nginx is built, and both are solved by a single APT package.

Stock nginx from the Debian and Ubuntu archives ships without --with-http_v3_module. The ondrej/nginx PPA fares no better on current LTS releases. Even when someone manages to self-build HTTP/3 support, they hit a second, nastier bug: upstream NGINX 1.25.0 and newer silently drops about half of new QUIC connections after nginx -s reload when quic_bpf is on. F5 has sat on the fix for over a year.

The GetPageSpeed nginx package in our APT repository solves both: it compiles --with-http_v3_module on every supported codename (Ubuntu 20.04 through 24.04, Debian 12, Debian 13) using nginx’s built-in OpenSSL QUIC compatibility layer, carries our QUIC reuseport reload patch, ships a UFW profile that opens UDP 443, and generates a persistent QUIC host key on first install. It’s a drop-in replacement for the distro nginx, nginx-core, nginx-full, nginx-extras, and nginx-light packages, and the fastest path to NGINX HTTP/3 on Ubuntu or Debian that doesn’t involve hand-compiling.

What Is HTTP/3 and Why Use It?

HTTP/3 is the third major version of the Hypertext Transfer Protocol. Unlike HTTP/1.1 and HTTP/2 which ride on TCP, HTTP/3 uses QUIC, a transport protocol built on UDP. QUIC eliminates head-of-line blocking, supports 0-RTT connection resumption, and migrates connections across network changes without dropping them. On mobile networks the difference is immediately visible: a user walking from Wi-Fi to cellular no longer loses their session.

For a deeper architectural dive covering the RHEL/Rocky/AlmaLinux install path, see the companion guide: How to Install NGINX QUIC on CentOS, RHEL, Rocky Linux, AlmaLinux, and Fedora.

Why Stock Ubuntu and Debian NGINX Falls Short

Two things stand between a typical Ubuntu or Debian admin and a working NGINX HTTP/3 on Ubuntu deployment:

  1. The binary has no --with-http_v3_module. If nginx -V 2>&1 | grep http_v3 returns nothing, no amount of configuration will turn HTTP/3 on. Debian, Ubuntu archives, and ondrej/nginx do not ship this flag on their current LTS builds.
  2. The QUIC reuseport reload bug. Even when HTTP/3 is compiled in, upstream NGINX leaks reuseport sockets from exiting workers during a graceful reload, intercepting new QUIC Initial packets and dropping them. This is the nginx -s reload hazard documented in NGINX HTTP/3 Is Broken After Reload.

A common myth says you also need a QUIC-capable OpenSSL (3.2+ or quictls) just to build HTTP/3. You don’t. NGINX 1.25.2 and newer ship an OpenSSL QUIC compatibility layer that simulates the QUIC handshake on top of plain OpenSSL 1.1.1+ using TLS custom extensions. The --with-http_v3_module configure step probes three tiers: OpenSSL 3.5.1+ native QUIC, BoringSSL/quictls/LibreSSL, and finally the built-in compat layer. Every Ubuntu and Debian codename we target satisfies at least the third tier.

NGINX HTTP/3 Packages by GetPageSpeed (APT)

The GetPageSpeed nginx APT package addresses every point above and is the recommended route to NGINX HTTP/3 on Ubuntu and Debian today:

Distribution Support Matrix

Distribution Codename System OpenSSL HTTP/3 in our package
Ubuntu 20.04 LTS focal 1.1.1 Yes (compat layer)
Ubuntu 22.04 LTS jammy 3.0.2 Yes (compat layer)
Ubuntu 24.04 LTS noble 3.0.13 Yes (compat layer)
Debian 12 bookworm 3.0.x Yes (compat layer)
Debian 13 trixie 3.5.x Yes (native QUIC API)

0-RTT Support: Coming via quictls

There’s one HTTP/3 feature the compat layer cannot deliver: 0-RTT connection resumption. NGINX’s ssl_early_data on; requires a QUIC-aware OpenSSL backend, either OpenSSL 3.5.1+ natively or a BoringSSL-like library such as quictls. On current-stack Ubuntu LTS releases (system OpenSSL 1.1.1, 3.0.2, or 3.0.13) that means 0-RTT is off even after our package is installed. Regular HTTP/3 still works exactly as documented here; only the zero-round-trip resumption path is unavailable.

We already ship a quictls package in our APT repository for every codename above: quictls, quictls-libs, quictls-dev, and quictls-static. It’s a co-installable fork of OpenSSL 3.1.x with QUIC APIs, layered at /usr/lib/<multiarch>/quictls/ so your system libssl3 is never touched. Rebuilding the GetPageSpeed nginx package against quictls on Ubuntu codenames is on the roadmap and will unlock 0-RTT out of the box.

Want 0-RTT NGINX HTTP/3 on Ubuntu 24.04, 22.04, 20.04, or Debian 12? Tell us. The more users voice demand, the sooner we prioritize the quictls-linked rebuild. Reach out via our contact form or email info@getpagespeed.com and mention which codename you need. It directly influences the build queue.

Install NGINX with HTTP/3 on Ubuntu or Debian

All commands below run as root or via sudo. The identical procedure works on focal, jammy, noble, bookworm, and trixie.

Step 1: Install the GetPageSpeed 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

Step 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 the nginx mainline branch, add a second line:

echo "deb [signed-by=/etc/apt/keyrings/getpagespeed.gpg] https://extras.getpagespeed.com/${distro} ${codename}-mainline main" \
  | sudo tee -a /etc/apt/sources.list.d/getpagespeed-extras.list

Step 3: Pin Our nginx Over Distro and Ondrej

This is critical. Without the pin, apt may prefer a higher-versioned stock nginx over ours:

sudo tee /etc/apt/preferences.d/getpagespeed-nginx.pref > /dev/null <<'EOF'
Package: nginx nginx-common nginx-core nginx-full nginx-extras nginx-light nginx-module-* libnginx-mod-*
Pin: origin extras.getpagespeed.com
Pin-Priority: 1001
EOF

Step 4: Install

sudo apt-get update
sudo apt-get install nginx

Verify HTTP/3 Is Compiled In

The single authoritative check:

nginx -V 2>&1 | tr ' ' '\n' | grep http_v3

Expected output on every supported codename:

--with-http_v3_module

Also confirm the package source and version:

dpkg -s nginx | grep -E 'Maintainer|Version'

You should see Maintainer: GetPageSpeed LLC <builder@getpagespeed.com> and a version string like 1:1.28.3-11~gps1+ubuntu2404+stable (noble), +ubuntu2204+stable (jammy), +ubuntu2004+stable (focal), +deb12+stable (bookworm), or +deb13+stable (trixie).

If http_v3_module is absent, the pin isn’t taking effect and apt installed the distro nginx. Re-check with apt-cache policy nginx.

Minimal HTTP/3 Server Block

Drop this into /etc/nginx/conf.d/h3-example.conf and adjust the hostname and certificate paths:

server {
    listen 443 ssl;              # TCP listener for HTTP/1.1 and HTTP/2
    listen 443 quic reuseport;   # UDP listener for QUIC and HTTP/3
    listen [::]:443 ssl;
    listen [::]:443 quic reuseport;

    server_name example.com;

    ssl_protocols       TLSv1.3;   # QUIC requires TLS 1.3
    ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    http2 on;
    http3 on;

    add_header Alt-Svc 'h3=":443"; ma=86400';

    root /var/www/html;
    index index.html;
}

Reload:

sudo nginx -t && sudo systemctl reload nginx

Multiple Virtual Hosts: reuseport Goes on One Server Block

listen 443 quic reuseport must appear exactly once per address family. Put it on your first (or default_server) block and use plain listen 443 quic; on the rest:

server {
    listen 443 ssl default_server;
    listen 443 quic reuseport default_server;
    server_name example.com;
    # ...
}

server {
    listen 443 ssl;
    listen 443 quic;
    server_name example.org;
    # ...
}

System Tuning: UDP Buffer Sizes

QUIC throughput is bottlenecked by Linux’s default UDP socket buffers. Raise them by writing /etc/sysctl.d/99-quic.conf:

# Increase UDP buffers for QUIC (HTTP/3) performance
net.core.rmem_max = 7500000
net.core.wmem_max = 7500000
net.core.rmem_default = 7500000
net.core.wmem_default = 7500000

Apply immediately:

sudo sysctl -p /etc/sysctl.d/99-quic.conf

Kernel-Assisted QUIC: quic_bpf and quic_gso

NGINX exposes two kernel-offload directives. Add them at the top of /etc/nginx/nginx.conf (main context) or http context respectively:

# Main context, before events {}
quic_bpf on;
http {
    quic_gso on;
    quic_host_key /etc/nginx/quic.key;
    # ...
}

quic_bpf on attaches a BPF program that steers QUIC packets to the correct worker by Destination Connection ID. Requires kernel 5.7+ and CAP_SYS_ADMIN on the nginx master process. Every supported codename’s stock kernel satisfies both. quic_gso on enables UDP segmentation offload, reducing per-packet syscall overhead. Requires kernel 4.18+.

This is where the reload fix earns its keep. quic_bpf on is exactly the combination that exposes the upstream reuseport reload bug. With the stock nginx, reloading a busy HTTP/3 server under quic_bpf drops roughly half of new connections for several seconds. With the GetPageSpeed nginx package, nginx -s reload is transparent to clients: the exiting worker removes itself from the reuseport group before it stops accepting. We verified this end-to-end on Ubuntu 24.04 noble (system OpenSSL 3.0.13, nginx built via the OpenSSL compat layer): 298 consecutive curl --http3 requests issued during three back-to-back nginx -s reload calls under quic_bpf on, and all 298 returned HTTP/3 200 with zero drops.

Persistent QUIC Host Key

Without quic_host_key, NGINX generates a random key at startup. Every restart invalidates all address validation tokens issued by the previous process, forcing clients through an extra round-trip. The GetPageSpeed postinst creates /etc/nginx/quic.key on first install:

ls -l /etc/nginx/quic.key
# -rw-r----- 1 root www-data 32 ...

Reference it from the http context (shown in the snippet above) and address validation tokens survive reloads and restarts.

Firewall: UFW

Our package ships a Nginx QUIC UFW profile that opens TCP 80, TCP 443, and UDP 443 in one rule:

sudo ufw app list | grep Nginx
# Nginx Full
# Nginx HTTP
# Nginx HTTPS
# Nginx QUIC

sudo ufw allow 'Nginx QUIC'
sudo ufw reload

If you prefer bare ports:

sudo ufw allow 443/tcp
sudo ufw allow 443/udp

UDP 443 is the line most Ubuntu admins forget when rolling out NGINX HTTP/3 on Ubuntu. Without it, browsers never get past the initial h2 handshake.

DNS HTTPS Records for Instant HTTP/3

Browsers discover HTTP/3 in two ways: the Alt-Svc response header (requires a first h2 request) and DNS HTTPS (type 65) records (RFC 9460), which skip the bootstrap entirely.

If you sit behind Cloudflare with the orange cloud on, HTTPS records are synthesized automatically. For other DNS providers, add:

example.com.   300   IN   HTTPS   1 . alpn="h3,h2"

Verify:

dig example.com HTTPS +short

Expected:

1 . alpn="h3,h2"

Verification

curl

On Debian 13 trixie, the stock curl (8.14) is built with HTTP/3 support. Ubuntu 20.04 through 24.04 and Debian 12 ship older curl builds without HTTP/3. Check yours with:

curl -V | grep -o HTTP3

If present, negotiate directly:

curl --http3 -sv https://example.com/ -o /dev/null -w 'protocol=%{http_version} code=%{http_code}\n'

A working response looks like:

protocol=3 code=200

On codenames whose stock curl lacks HTTP/3, use a browser or install an HTTP/3-capable curl build from a backports repository.

Browser DevTools

Open DevTools, Network tab, enable the Protocol column. Visit your site twice (the first request bootstraps over h2; the second should show h3).

Online Tester

http3check.net gives a one-shot external check including HTTPS DNS record presence.

Access Log

Add $server_protocol to your log_format to see HTTP/3 requests inline:

log_format h3aware '$remote_addr - $remote_user [$time_local] '
                   '"$request" $status $body_bytes_sent '
                   '"$http_referer" "$http_user_agent" '
                   '"$server_protocol"';

access_log /var/log/nginx/access.log h3aware;

HTTP/3 requests will log as HTTP/3.0.

Troubleshooting

Connections drop after nginx -s reload

If you are on our package, this should not happen. Confirm first:

dpkg -s nginx | grep Maintainer

Must show GetPageSpeed LLC. If it does and you still see drops, file an issue; we’ll want to know.

nginx -V shows no --with-http_v3_module

The pin isn’t in effect and apt installed the distro nginx or a ondrej/nginx build. Check:

apt-cache policy nginx

The extras.getpagespeed.com line should show priority 1001. If not, fix /etc/apt/preferences.d/getpagespeed-nginx.pref and reinstall:

sudo apt-get install --reinstall nginx

unknown directive "http3"

Same root cause: HTTP/3 is not compiled in. Verify as above.

Browser still shows HTTP/2

One of: missing DNS HTTPS record, missing Alt-Svc header, or cold bootstrap cache. Check the Alt-Svc header with curl -sI https://example.com/ | grep -i alt-svc.

bind() failed (13: Permission denied) on UDP 443

AppArmor is the usual culprit on Ubuntu. Check journalctl -k | grep -i apparmor right after the failed reload. If you see denials against /usr/sbin/nginx, either adjust the profile or set it to complain mode temporarily (sudo aa-complain /etc/apparmor.d/usr.sbin.nginx).

UDP buffer errors in journalctl -u nginx

You forgot to apply 99-quic.conf. 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, check limits

Kernel is older than 5.7, the nginx master lacks CAP_SYS_ADMIN, or you’re running inside an unprivileged container. Drop quic_bpf on;, upgrade the kernel, or run the container with the capability granted.

ssl_early_data has no effect

This is not a bug. 0-RTT requires OpenSSL 3.5.1+ natively or a QUIC-capable OpenSSL backend such as quictls. On Ubuntu 20.04/22.04/24.04 and Debian 12 the system OpenSSL does not meet this requirement. Debian 13 trixie (OpenSSL 3.5.x) supports 0-RTT with our stock package; for Ubuntu, see the quictls section above.

Conclusion

One APT package delivers production-ready NGINX HTTP/3 on Ubuntu 20.04, 22.04, 24.04, and Debian 12 and 13: HTTP/3 compiled in via the OpenSSL compatibility layer, the reload-drop bug fixed, a persistent QUIC host key, and a UFW profile for UDP 443. The only feature still gated on OpenSSL backend is 0-RTT resumption, which is on the way via our quictls-linked rebuild for non-trixie codenames. Tell us which codename needs 0-RTT most and it moves up the queue.

For the RPM-based equivalent of this guide, see How to Install NGINX QUIC on CentOS, RHEL, Rocky Linux, AlmaLinux, and Fedora. For the deep dive on the reload bug we patched, see NGINX HTTP/3 Is Broken After Reload — Here’s the Fix F5 Won’t Ship.

D

Danila Vershinin

Founder & Lead Engineer

NGINX configuration and optimizationLinux system administrationWeb performance engineering

10+ years NGINX experience • Maintainer of GetPageSpeed RPM repository • Contributor to open-source NGINX modules

Exit mobile version