HTTP/3 is the third major version of the HTTP protocol — and it is genuinely fast. It ditches TCP entirely and runs over QUIC, a UDP-based transport that eliminates head-of-line blocking, reduces connection setup time, and handles packet loss better on mobile and high-latency networks. The result: pages load faster, especially on the first request and on unreliable connections.
The catch: enabling HTTP/3 on NGINX requires an OpenSSL build with QUIC patches. The official Debian and Ubuntu NGINX packages are not compiled with this. The myguard APT repository ships NGINX mainline linked against openssl-nginx — a dedicated OpenSSL build with full QUIC support — so HTTP/3 works out of the box.
What Is QUIC and Why Does It Matter?
To understand HTTP/3, you need to understand what was wrong with HTTP/2. HTTP/2 was a big improvement over HTTP/1.1 — it multiplexed multiple requests over a single TCP connection. But TCP has a fundamental flaw called head-of-line blocking: if one packet is lost, all streams on that connection stall until the lost packet is retransmitted. On a reliable fibre connection, this rarely matters. On a mobile connection dropping packets every few seconds, it’s brutal.
QUIC (Quick UDP Internet Connections) was designed by Google and standardised by the IETF to solve this. Key differences:
- UDP instead of TCP — QUIC handles its own reliability and ordering per stream, so packet loss in one stream doesn’t block others
- 0-RTT connection resumption — Returning visitors can resume a previous session with zero round-trip overhead. No TCP handshake, no TLS handshake. First byte arrives faster.
- Connection migration — QUIC connections survive IP address changes (like switching from WiFi to 4G) because the connection is identified by a connection ID, not a 5-tuple. HTTP/2 over TCP would need a full reconnect.
- TLS 1.3 baked in — QUIC always uses TLS 1.3. There’s no option to run QUIC without encryption. Security is not optional.
- Faster initial handshake — QUIC combines the transport and TLS handshake into a single round trip. TCP + TLS 1.3 takes at minimum two round trips.
Real-World Performance Numbers
HTTP/3 performance gains depend heavily on network conditions:
- Fast wired connections: Minimal improvement — 0-RTT resumption saves ~20ms, head-of-line blocking is rarely triggered
- Mobile / 4G: 15–25% improvement in page load times. Packet loss rates of 1–2% that are normal on cellular cause significant HTTP/2 stalls
- High-latency connections (100ms+ RTT): 20–40% improvement from the reduced handshake round trips
- Lossy networks (>2% packet loss): Up to 50% improvement — HTTP/3’s per-stream loss recovery is dramatically better
For a primarily desktop audience on good connections, HTTP/3 is a marginal win. For a mobile-heavy audience or international traffic from regions with packet loss, it’s significant. Enable it — browsers fall back to HTTP/2 automatically if QUIC doesn’t work.
Prerequisites
- Debian 12/13 or Ubuntu 22.04/24.04/26.04
- NGINX from the myguard repository (standard Debian/Ubuntu NGINX does not support HTTP/3)
- A valid TLS certificate (HTTP/3 requires HTTPS — use Let’s Encrypt or Certbot)
- UDP port 443 open in your firewall
Step 1 — Install NGINX from the myguard repository
If you haven’t already added the repository:
wget https://deb.myguard.nl/pool/myguard.deb
dpkg -i myguard.deb
apt-get update
apt-get install nginx
Verify HTTP/3 support is compiled in:
nginx -V 2>&1 | grep http3
You should see --with-http_v3_module in the output.
Step 2 — Open UDP port 443
QUIC runs on UDP. HTTP/3 will silently fall back to HTTP/2 if UDP 443 is blocked. Open it before configuring anything else.
# UFW
ufw allow 443/udp
# iptables
iptables -A INPUT -p udp --dport 443 -j ACCEPT
# nftables
nft add rule inet filter input udp dport 443 accept
Verify UDP 443 is open from an external machine:
nmap -sU -p 443 your-server-ip
# Open state means QUIC can work
Step 3 — Enable HTTP/3 in your NGINX server block
Add http3 on and the Alt-Svc header to your existing HTTPS server block. The Alt-Svc header tells browsers that HTTP/3 is available so they upgrade on the next request.
server {
listen 443 ssl;
listen 443 quic reuseport; # HTTP/3 listener
listen [::]:443 ssl;
listen [::]:443 quic reuseport;
http2 on;
http3 on;
http3_hq on; # HTTP/0.9 over QUIC (for compatibility)
quic_retry on; # Enables QUIC stateless retry
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# Required TLS settings for QUIC
ssl_protocols TLSv1.3;
ssl_early_data on; # 0-RTT resumption
# Tell browsers HTTP/3 is available
add_header Alt-Svc 'h3=":443"; ma=86400';
server_name example.com;
root /var/www/html;
}
Step 4 — Test and reload
nginx -t && systemctl reload nginx
Step 5 — Verify HTTP/3 is working
Use curl with HTTP/3 support, or check with an online tool:
# curl with HTTP/3 (requires curl built with quiche or ngtcp2)
curl --http3 https://example.com -I
# Or use the online HTTP/3 checker at https://http3check.net
You can also check the Chrome DevTools Network tab — the protocol column should show h3 instead of h2.
0-RTT: Speed Versus Replay Risk
The ssl_early_data on directive enables 0-RTT connection resumption. This is powerful but comes with a caveat you should understand before enabling it in production.
With 0-RTT, a returning visitor’s browser can send a request in the very first packet — no handshake required. The server processes it before the full handshake completes. This shaves one full round-trip off connection setup time, which on a 50ms latency connection saves 50ms on every revisit.
The risk: replay attacks. A network attacker who captures a 0-RTT packet can resend it to your server, causing the same request to be processed twice. For idempotent GET requests this is usually harmless. For POST requests (form submissions, API calls, purchases), a double-replay could cause duplicate operations.
Mitigations:
- Use the
$ssl_early_dataNGINX variable to detect 0-RTT requests and add anEarly-Data: 1header to upstream requests — your application can then reject non-idempotent 0-RTT requests - Alternatively, disable 0-RTT for specific locations (e.g. payment endpoints) with a
ssl_early_data offoverride inside thatlocationblock
location /api/ {
# Pass 0-RTT indicator to backend
proxy_set_header Early-Data $ssl_early_data;
proxy_pass http://backend;
}
HTTP/3 Behind a Load Balancer or CDN
If NGINX is behind a load balancer or CDN, HTTP/3 setup is slightly different:
- CDN with HTTP/3 support (Cloudflare, Fastly): The CDN handles HTTP/3 to the client; your origin NGINX can stay on HTTP/1.1 or HTTP/2 for the CDN→origin connection. No QUIC needed on origin.
- Layer-4 load balancer (TCP/UDP passthrough): You need UDP 443 forwarded to your backend NGINX. UDP passthrough — not just TCP — must be configured. HAProxy 2.6+, nginx stream module, and most cloud NLBs support this.
- Layer-7 proxy terminating TLS: The L7 proxy needs HTTP/3 support to terminate QUIC. If the proxy doesn’t support QUIC, it can’t forward HTTP/3 — the connection will fall back to HTTP/2.
NGINX Configuration Tuning for HTTP/3
http {
# QUIC-specific buffer tuning
quic_gso on; # Enable generic segmentation offload for QUIC
quic_host_key /etc/nginx/quic-host.key; # Optional: QUIC connection ID encryption
# For high-traffic servers: tune UDP socket buffers
# (in /etc/sysctl.conf, not nginx.conf)
# net.core.rmem_max = 16777216
# net.core.wmem_max = 16777216
# net.core.rmem_default = 4194304
# net.core.wmem_default = 4194304
}
For servers handling heavy HTTP/3 traffic (>10k concurrent QUIC connections), also increase the UDP socket buffer limits in the OS. QUIC is more demanding of UDP buffers than TCP is of TCP buffers because packet pacing happens in userspace:
echo "net.core.rmem_max=16777216" >> /etc/sysctl.conf
echo "net.core.wmem_max=16777216" >> /etc/sysctl.conf
sysctl -p
HTTP/3 with Angie
The myguard Angie packages also include full HTTP/3 support. The configuration syntax is identical to NGINX — same listen 443 quic reuseport, same http3 on, same Alt-Svc header. If you want HTTP/3 plus native ACME (Let’s Encrypt without Certbot) in the same package, Angie is the cleaner choice.
# Angie installation
apt-get install angie
# Same HTTP/3 config works unchanged
angie -t && systemctl reload angie
Common Issues
Alt-Svc header. Reload the page — the second visit should use HTTP/3. If it still doesn’t, check that UDP 443 is actually open with nmap -sU -p 443 your-server-ip. Also check that the Alt-Svc header is actually being sent: curl -I https://example.com | grep alt-svcapt-cache policy nginx to check which version is installed. The myguard version should show deb.myguard.nl as the source. Install it with the steps in Step 1 above.ssl_protocols TLSv1.3; is set (or at minimum TLSv1.2 TLSv1.3). Also verify your certificate is valid and not expired with openssl s_client -connect example.com:443 -tls1_3.listen 443 quic reuseport syntax. Angie adds native ACME (no Certbot) and a JSON status API on top.$ssl_early_data variable and pass an Early-Data: 1 header to your backend so it can reject replayed write requests.Related Posts
- NGINX modules overview — all 50+ dynamic modules available via APT, including the QUIC-related ones
- openssl-nginx: A Dedicated OpenSSL Build for NGINX and Angie — why a separate OpenSSL is needed for QUIC and what it enables
- TLS Configuration for NGINX and Angie — complete TLS hardening guide to pair with HTTP/3, including 0-RTT considerations
- Angie modules overview — if you want HTTP/3 with native ACME, Angie is worth a look
- How to add the myguard APT repository — two-minute setup guide