Why Upgrade from HTTP/2 to HTTP/3?
HTTP/3 replaces TCP with QUIC (a UDP-based transport protocol) to eliminate head-of-line blocking at the transport layer — the last major latency bottleneck that HTTP/2 could not solve. Even with HTTP/2 multiplexing, a single lost TCP packet stalls all streams on a connection. QUIC's per-stream reliability removes that constraint entirely.
Key benefits over HTTP/2:
- No head-of-line blocking — packet loss affects only the stream it belongs to
- 0-RTT connection resumption — returning clients send data immediately, no handshake wait
- Connection migration — QUIC connections survive IP changes (mobile roaming, Wi-Fi handoff)
- Built-in TLS 1.3 — security is inseparable from the transport; no plain-text QUIC
- Faster handshake — 1-RTT for new connections (vs TCP+TLS 1.3 which is also 1-RTT but with two separate handshakes)
Browser support as of 2025: Chrome 87+, Firefox 88+, Safari 14+, Edge 87+ — covering ~95%+ of global users. The migration is additive: you keep HTTP/2 for unsupported clients.
Step 1: Readiness Assessment
Client Support Matrix
# Check current TLS version distribution from your access logs:
awk '{print $NF}' /var/log/nginx/access.log | sort | uniq -c | sort -rn
# Or with curl to test if your server already speaks HTTP/3:
curl -I --http3 https://your-domain.com
UDP Firewall Rules
QUIC runs over UDP port 443. Many corporate firewalls block UDP 443 by default, which is why HTTP/3 always falls back to HTTP/2 gracefully. Before enabling:
# Verify UDP 443 is open on your server:
sudo ufw allow 443/udp
# AWS Security Group — add inbound rule:
# Type: Custom UDP, Port: 443, Source: 0.0.0.0/0
# Test UDP reachability from an external host:
nc -u -z your-domain.com 443 && echo 'UDP 443 open'
Load Balancer QUIC Support
| Load Balancer | HTTP/3 Support | Notes |
|---|---|---|
| AWS ALB | No (as of 2025) | Terminate at CloudFront instead |
| GCP Cloud Load Balancer | Yes | Enable in backend config |
| Azure Application Gateway | No | Use Azure Front Door |
| HAProxy 2.6+ | Yes | `h3` bind option |
| nginx (with quic module) | Yes | Requires rebuild or distro package |
If your load balancer does not support QUIC, terminate HTTP/3 at the CDN layer (Cloudflare, Fastly, Akamai) and use HTTP/2 or HTTP/1.1 between the CDN and your origin. This is the most common production pattern.
Step 2: Server Configuration
Nginx with QUIC Module
Standard Nginx packages do not include the QUIC module. You need either the official nginx QUIC branch package or a custom build:
# Ubuntu 22.04+ — install nginx-quic package (nginx.org repository):
curl https://nginx.org/keys/nginx_signing.key | sudo apt-key add -
echo 'deb https://packages.nginx.org/nginx-quic/ubuntu focal nginx-quic' \
| sudo tee /etc/apt/sources.list.d/nginx-quic.list
sudo apt update && sudo apt install nginx-quic
Configuration:
server {
# HTTP/3 (QUIC) on UDP 443
listen 443 quic reuseport;
# HTTP/2 and HTTP/1.1 on TCP 443 (fallback)
listen 443 ssl;
ssl_certificate /etc/ssl/certs/your-domain.crt;
ssl_certificate_key /etc/ssl/private/your-domain.key;
ssl_protocols TLSv1.3; # HTTP/3 requires TLS 1.3
# Enable HTTP/3
http3 on;
quic_retry on; # Enables address validation (recommended)
# Advertise HTTP/3 to clients (Alt-Svc)
add_header Alt-Svc 'h3=":443"; ma=86400';
location / {
proxy_pass http://backend;
}
}
Caddy — Automatic HTTP/3
Caddy enables HTTP/3 automatically with zero configuration since version 2.5:
# Caddyfile — HTTP/3 enabled by default on port 443
your-domain.com {
reverse_proxy localhost:8080
# That's it. Caddy handles TLS, HTTP/2, and HTTP/3 automatically.
}
Verify HTTP/3 is active:
curl -sI --http3 https://your-domain.com | grep -i 'alt-svc\|HTTP'
# Expected:
# HTTP/3 200
# alt-svc: h3=":443"; ma=2592000
Step 3: CDN Enablement
Cloudflare (Automatic)
Cloudflare enables HTTP/3 for all zones with a single toggle — no origin changes needed:
# Enable via Cloudflare API:
curl -X PATCH \
'https://api.cloudflare.com/client/v4/zones/{zone_id}/settings/http3' \
-H 'Authorization: Bearer {api_token}' \
-H 'Content-Type: application/json' \
-d '{"value": "on"}'
Or in the dashboard: Speed → Optimization → Protocol Optimization → HTTP/3 (with QUIC).
AWS CloudFront
As of 2025, CloudFront does not natively support HTTP/3. Use CloudFront in front of Cloudflare, or wait for native support. The workaround:
Users → Cloudflare (HTTP/3 termination) → CloudFront (HTTP/2) → Origin
Fastly QUIC
Enable via the Fastly control panel under Settings → HTTP/3. Fastly supports QUIC natively across all POPs and handles the Alt-Svc advertisement automatically.
Step 4: Alt-Svc Advertisement
Clients discover HTTP/3 availability through the Alt-Svc response header or DNS HTTPS records. The browser remembers this for the TTL specified by ma (max-age in seconds).
HTTP Response Header
Alt-Svc: h3=":443"; ma=86400, h3-29=":443"; ma=86400
h3— the final HTTP/3 spec (RFC 9114)h3-29— draft 29 (required for compatibility with older Chrome versions)ma=86400— cache this advertisement for 24 hours
DNS HTTPS Record (SVCB)
A faster alternative: the browser checks the DNS HTTPS record *before* connecting, enabling 0-RTT HTTP/3 on the very first request:
# DNS HTTPS record (SVCB type 65):
# your-domain.com. 300 IN HTTPS 1 . alpn="h3,h2" ipv4hint=1.2.3.4
# Add via Cloudflare API:
curl -X POST \
'https://api.cloudflare.com/client/v4/zones/{zone_id}/dns_records' \
-H 'Authorization: Bearer {api_token}' \
-H 'Content-Type: application/json' \
-d '{"type":"HTTPS","name":"your-domain.com","data":{"priority":1,"target":".","value":"alpn=\"h3,h2\""}}'
Graceful Fallback Chain
Client attempts HTTP/3 (UDP 443)
├─ Success → use HTTP/3 for session lifetime
└─ Timeout/blocked → fall back to HTTP/2 (TCP 443) within ~300ms
└─ HTTP/2 unavailable → HTTP/1.1
The fallback is built into all HTTP/3 client implementations. Your users on corporate networks with UDP 443 blocked will transparently use HTTP/2 with no user-visible impact.
Step 5: Monitoring QUIC Metrics
Nginx QUIC Metrics
# Enable extended access log variables:
log_format quic_log '$remote_addr - [$time_local] "$request" '
'$status $body_bytes_sent '
'quic=$http3 ' # h3 or empty string
'rtt=$quic_0rtt'; # 1 if 0-RTT was used
access_log /var/log/nginx/access.log quic_log;
Key Metrics to Track
# HTTP/3 adoption rate (percentage of requests using QUIC):
grep 'quic=h3' /var/log/nginx/access.log | wc -l
# 0-RTT usage (connection resumption efficiency):
grep 'rtt=1' /var/log/nginx/access.log | wc -l
# Compare TTFB between HTTP versions (CloudWatch or Datadog query):
# Split metric: http_version = [h3, h2, h1.1]
Target adoption rates: 50-70% on mobile (high packet loss environment), 20-40% on stable desktop connections (where HTTP/2 is nearly as fast). Low adoption usually means UDP 443 is blocked by an upstream firewall.
Rollback
HTTP/3 rollback is instant — simply remove the Alt-Svc header and stop listening on UDP 443. Clients fall back to HTTP/2 after their cached Alt-Svc TTL expires (or immediately on the next connection attempt that times out on QUIC).