How to Enable HTTP/3 in Nginx

How to Enable HTTP/3 in Nginx

How to Enable HTTP/3 in Nginx

HTTP/3 has been the thing I’ve been most excited to roll out on my own servers over the past couple of years, and it’s finally reached the point where enabling it is genuinely practical rather than a bleeding-edge experiment. Unlike HTTP/2, which just changed how data was framed over an existing TCP connection, HTTP/3 replaces TCP entirely with QUIC, a transport protocol built on UDP. That’s a bigger architectural shift than it sounds, and it’s exactly why enabling it in Nginx isn’t quite as simple as flipping a flag — until relatively recently, it required a custom-compiled Nginx build.

Here’s the good news: as of Nginx 1.25.0, QUIC and HTTP/3 support is built into mainline Nginx, no third-party module or custom compile required (assuming your OS package or the official Nginx repo ships a build with --with-http_v3_module, which most current ones do). I’ll walk through the whole process, including how to check whether your installed Nginx even supports it, and what to do if it doesn’t.

What HTTP/3 Actually Changes

Quick primer, because understanding this helps you configure it correctly:

The upshot: HTTP/3 is particularly beneficial for clients on lossy or high-latency networks — mobile users are the biggest beneficiaries.

Requirements

Step 1: Check If Your Nginx Already Supports HTTP/3

nginx -V 2>&1 | grep -o 'with-http_v3_module'

If that prints with-http_v3_module, you’re set. If nothing prints, you’ll need to either install from the official Nginx repository (which ships QUIC-enabled builds) or compile from source.

Step 2: Install Nginx from the Official Repo (If Needed)

On Ubuntu/Debian:

sudo apt install curl gnupg2 ca-certificates lsb-release ubuntu-keyring -y

curl https://nginx.org/keys/nginx_signing.key | gpg --dearmor \
    | sudo tee /usr/share/keyrings/nginx-archive-keyring.gpg >/dev/null

echo "deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] \
http://nginx.org/packages/mainline/ubuntu $(lsb_release -cs) nginx" \
    | sudo tee /etc/apt/sources.list.d/nginx.list

sudo apt update
sudo apt install nginx -y

Using the mainline branch (rather than stable) is intentional here — HTTP/3 support matured fastest in mainline releases. Verify again:

nginx -V 2>&1 | grep -o 'with-http_v3_module'

Step 3: Confirm OpenSSL Compatibility

nginx -V 2>&1 | grep -o 'OpenSSL [0-9.]*'

If you see OpenSSL 3.x, you’re good — recent OpenSSL versions include the QUIC TLS extensions Nginx needs. If you’re stuck on an older OpenSSL and can’t upgrade the system package, the alternative is building Nginx against quictls, a QUIC-patched OpenSSL fork, but that’s a from-source build I’d only recommend if the package repo route isn’t an option for your OS.

Step 4: Get a TLS Certificate

If you don’t already have one:

sudo apt install certbot python3-certbot-nginx -y
sudo certbot certonly --nginx -d example.com -d www.example.com

I’m using certonly here rather than the full --nginx auto-config flow, since we’re about to write a fairly specific HTTP/3-aware server block by hand.

Step 5: Configure Nginx for HTTP/3

sudo nano /etc/nginx/sites-available/example.com
server {
    listen 443 quic reuseport;
    listen 443 ssl;
    listen [::]:443 quic reuseport;
    listen [::]:443 ssl;

    http2 on;
    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.3;

    # Advertise HTTP/3 support to clients via the Alt-Svc header
    add_header Alt-Svc 'h3=":443"; ma=86400';
    add_header X-QUIC "h3" always;

    root /var/www/example.com/public;
    index index.html;

    location / {
        try_files $uri $uri/ =404;
    }
}

server {
    listen 80;
    server_name example.com www.example.com;
    return 301 https://$host$request_uri;
}

Let’s unpack the important pieces:

Step 6: Open the Firewall for UDP

This is the step people most often forget, because it’s easy to assume “port 443” only means TCP.

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

If you’re behind a cloud provider’s security groups (AWS, GCP, etc.), make sure UDP 443 is allowed there too — a local ufw rule won’t help if the cloud firewall drops the packets first.

Step 7: Test and Reload

sudo nginx -t
sudo systemctl reload nginx

Testing Your Setup

The most reliable test is curl with explicit HTTP/3 support (requires a curl build compiled with --with-http3, which isn’t universal — check with curl -V | grep HTTP3):

curl --http3 -I https://example.com

If your local curl doesn’t support HTTP/3, use an online checker instead — search for “HTTP/3 test” and use a reputable one, or check via your browser’s DevTools:

  1. Open Chrome DevTools → Network tab
  2. Right-click the column headers → enable the “Protocol” column
  3. Reload the page
  4. Look for h3 in the Protocol column for your requests

Firefox has similar DevTools support under the Network tab’s protocol column.

You can also verify the Alt-Svc header is being sent correctly:

curl -I https://example.com | grep -i alt-svc

Expect something like alt-svc: h3=":443"; ma=86400.

Troubleshooting Common Issues

Browser never upgrades to HTTP/3 — Give it a moment; browsers cache the Alt-Svc advertisement and use QUIC on the next connection, not necessarily the first one that sees the header. Also confirm UDP 443 isn’t blocked somewhere in the path (corporate networks and some ISPs block or throttle UDP more aggressively than TCP).

nginx: [emerg] unknown directive "listen ... quic" — Your Nginx binary wasn’t built with --with-http_v3_module. Revisit Step 1/2.

Certificate errors specific to QUIC — Double check OpenSSL version; QUIC’s TLS handshake genuinely needs OpenSSL 3.x’s QUIC-related APIs, and errors here often look like generic SSL handshake failures rather than anything obviously QUIC-related.

Works locally, fails from outside — Almost always a cloud/network firewall dropping UDP 443. Test with nc -u -zv your-server-ip 443 from an external machine, or check your cloud provider’s security group / firewall rules console directly.

High CPU on the QUIC listener — QUIC does more per-packet crypto work than plain TCP+TLS in some configurations. Make sure reuseport is set so load spreads across worker processes, and keep Nginx and OpenSSL updated, since QUIC performance optimizations land frequently in newer releases.

Security Considerations

limit_conn_zone $binary_remote_addr zone=addr:10m;

location / {
    limit_conn addr 50;
}

Performance Tips

sudo sysctl -w net.core.rmem_max=2500000
sudo sysctl -w net.core.wmem_max=2500000

Make these persistent by adding them to /etc/sysctl.conf.

Real-World Use Cases

Best Practices Recap

HTTP/3 is one of those upgrades that’s mostly invisible to your users in the best possible way — pages just feel a little snappier, especially on mobile, without anyone needing to know why. Once the firewall and header pieces are in place, it’s a genuinely low-maintenance addition to a modern Nginx setup.

Exit mobile version