Migration & Upgrades

Migrating from HTTP/2 to HTTP/3: QUIC Deployment Guide

How to enable HTTP/3 on your infrastructure — server configuration (Nginx, Caddy, Cloudflare), client support detection, and gradual rollout with Alt-Svc.

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 BalancerHTTP/3 SupportNotes
AWS ALBNo (as of 2025)Terminate at CloudFront instead
GCP Cloud Load BalancerYesEnable in backend config
Azure Application GatewayNoUse Azure Front Door
HAProxy 2.6+Yes`h3` bind option
nginx (with quic module)YesRequires 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).

Related Protocols

Related Glossary Terms

More in Migration & Upgrades