HTTP/3 and QUIC Configuration Guide
HTTP/3 uses the QUIC transport protocol over UDP instead of TCP, reducing latency and improving performance on unreliable connections. This guide covers enabling HTTP/3 and QUIC on Nginx and Caddy on Linux, configuring UDP firewall rules, verifying TLS 1.3, and testing client compatibility.
Prerequisites
- Ubuntu 20.04+/Debian 11+ or CentOS 8+/Rocky Linux 8+
- A valid SSL/TLS certificate (Let's Encrypt works)
- Root or sudo access
- UDP port 443 open in your firewall
- Nginx 1.25.0+ (for native QUIC support) or Caddy
Understanding HTTP/3 and QUIC
Key differences from HTTP/2:
- Uses UDP instead of TCP
- Built-in TLS 1.3 (TLS is part of QUIC, not separate)
- Eliminates head-of-line blocking at the transport layer
- Faster connection establishment (0-RTT available)
- Better performance on lossy connections (mobile networks)
Browsers fall back to HTTP/2 or HTTP/1.1 automatically if QUIC is blocked or unavailable.
Enabling HTTP/3 on Nginx
Nginx 1.25.0+ includes QUIC/HTTP3 support as an experimental feature. Check your version:
nginx -v
# nginx version: nginx/1.25.x
If you have Nginx 1.25+, QUIC may already be compiled in:
nginx -V 2>&1 | grep -o 'with-http_v3_module'
# Should output: with-http_v3_module
Building Nginx with QUIC Support
If your Nginx doesn't include QUIC, build it from the Nginx mainline source:
# Install build dependencies
sudo apt install -y build-essential libssl-dev libpcre3-dev zlib1g-dev \
libgd-dev libxslt1-dev cmake ninja-build golang-go mercurial
# Download Nginx mainline source
NGINX_VER=1.25.3
wget "https://nginx.org/download/nginx-${NGINX_VER}.tar.gz"
tar xzf "nginx-${NGINX_VER}.tar.gz"
# Get quiche (Cloudflare's QUIC implementation) or use OpenSSL 3.x with QUIC
# Option 1: Use nginx-quic branch (for nginx with BoringSSL)
hg clone https://hg.nginx.org/nginx-quic
cd nginx-quic
# Option 2: Build with OpenSSL 3.x (simpler)
wget https://www.openssl.org/source/openssl-3.1.0.tar.gz
tar xzf openssl-3.1.0.tar.gz
# Build Nginx with HTTP/3
./auto/configure \
--with-http_v3_module \
--with-http_ssl_module \
--with-http_v2_module \
--with-cc-opt="-I../openssl-3.1.0/include" \
--with-ld-opt="-L../openssl-3.1.0" \
--with-http_gzip_static_module \
--prefix=/etc/nginx \
--sbin-path=/usr/sbin/nginx \
--modules-path=/usr/lib64/nginx/modules \
--conf-path=/etc/nginx/nginx.conf \
--error-log-path=/var/log/nginx/error.log \
--http-log-path=/var/log/nginx/access.log \
--pid-path=/var/run/nginx.pid
make -j$(nproc)
sudo make install
Alternatively, use the NGINX official packages which include QUIC in recent mainline builds:
# Add NGINX mainline repo and install
sudo apt install -y nginx-core # May include QUIC depending on version
Nginx HTTP/3 Configuration
server {
# HTTP/2 on TCP (standard HTTPS)
listen 443 ssl;
http2 on;
# HTTP/3 on UDP (QUIC)
listen 443 quic reuseport;
server_name www.yourdomain.com;
ssl_certificate /etc/letsencrypt/live/www.yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/www.yourdomain.com/privkey.pem;
# TLS 1.3 required for QUIC
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
# Enable QUIC-specific TLS settings
ssl_early_data on; # 0-RTT support (use with caution - replay attacks)
# Advertise HTTP/3 support to clients via Alt-Svc header
add_header Alt-Svc 'h3=":443"; ma=86400';
add_header QUIC-Status $quic; # Optional: indicate QUIC usage
# Standard configuration continues...
root /var/www/html;
index index.html;
location / {
try_files $uri $uri/ =404;
}
}
# Redirect HTTP to HTTPS
server {
listen 80;
server_name www.yourdomain.com;
return 301 https://$host$request_uri;
}
Key points:
listen 443 quic reuseport;enables QUIC on UDPadd_header Alt-Svctells browsers to try HTTP/3 next time- Both TCP (HTTP/2) and UDP (HTTP/3) listeners are required
sudo nginx -t
sudo systemctl reload nginx
Enabling HTTP/3 on Caddy
Caddy enables HTTP/3 by default in recent versions - no special configuration needed:
# Install Caddy (includes HTTP/3 support)
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | \
sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | \
sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update && sudo apt install -y caddy
caddy version # Should be 2.6.0+
Basic Caddyfile with HTTP/3 enabled automatically:
# /etc/caddy/Caddyfile
www.yourdomain.com {
root * /var/www/html
file_server
# HTTP/3 is enabled by default
# Verify with: curl --http3 https://www.yourdomain.com -I
# Optional: explicitly configure
# protocols h3 h2 h1
encode gzip zstd
log {
output file /var/log/caddy/access.log
}
}
sudo systemctl reload caddy
# Check status
curl -I https://www.yourdomain.com | grep -i alt-svc
Firewall UDP Configuration
HTTP/3 requires UDP port 443 to be open:
# UFW
sudo ufw allow 443/udp
sudo ufw status
# iptables
sudo iptables -A INPUT -p udp --dport 443 -j ACCEPT
sudo iptables-save > /etc/iptables/rules.v4
# firewalld (CentOS/Rocky)
sudo firewall-cmd --permanent --add-port=443/udp
sudo firewall-cmd --reload
sudo firewall-cmd --list-ports | grep 443
Verify UDP port 443 is open from outside:
# From a remote machine
nc -u -zv your-server-ip 443
# Or use nmap
nmap -sU -p 443 your-server-ip
TLS 1.3 Requirements
QUIC requires TLS 1.3. Verify your Nginx/Caddy configuration:
# Verify TLS 1.3 is enabled in Nginx
openssl s_client -connect www.yourdomain.com:443 -tls1_3 2>/dev/null | \
grep -E 'Protocol|Cipher'
# Check OpenSSL version supports TLS 1.3
openssl version # Need 1.1.1+
# For Ubuntu 20.04+, OpenSSL 1.1.1 is included
# For CentOS 8+, OpenSSL 1.1.1 is included
Required Nginx TLS settings for QUIC:
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers off;
Testing HTTP/3
# Install curl with HTTP/3 support
# Ubuntu 22.04+ includes curl with HTTP/3 via ngtcp2
curl --http3 -I https://www.yourdomain.com
# If curl doesn't support HTTP/3, build from source or use Docker
docker run --rm -it ymuski/curl-http3 curl --http3 -I https://www.yourdomain.com
# Use nghttp3 tools
sudo apt install -y nghttp2-client
nghttp -nv https://www.yourdomain.com
# Check using Chromium (shows protocol in DevTools > Network > Protocol column)
# Chrome: chrome://net-internals/#quic
# Online test tools:
# https://www.http3check.net
# https://gf.dev/http3-test
Verify the Alt-Svc header is being sent:
curl -I https://www.yourdomain.com | grep -i alt-svc
# Expected: Alt-Svc: h3=":443"; ma=86400
Troubleshooting
HTTP/3 not working despite correct configuration:
# Check Nginx was compiled with QUIC
nginx -V 2>&1 | grep http_v3
# Check UDP 443 is actually open
sudo ss -ulnp | grep ':443'
# Check for firewall issues (common with cloud providers)
# AWS Security Groups, GCP Firewall rules - add UDP 443 explicitly
Alt-Svc header present but browsers don't upgrade:
Browsers maintain a QUIC blocklist per domain. If a previous connection failed, the domain may be blocked for hours. Clear with:
# Chrome: chrome://net-internals/#quic > Clear QUIC sessions
"ssl_early_data" causing issues:
0-RTT data is subject to replay attacks. Disable if you see unexpected behavior:
ssl_early_data off;
Caddy not showing HTTP/3 in responses:
Check that UDP 443 is open and Caddy is using a version that includes QUIC:
caddy version | grep -v "^$"
# Build info should include QUIC
journalctl -u caddy | grep -i quic
Conclusion
HTTP/3 with QUIC brings measurable latency improvements for users on high-latency or lossy connections, and modern browsers will adopt it automatically once you advertise support via the Alt-Svc header. Caddy provides the simplest path to HTTP/3 with zero extra configuration. For Nginx, ensure you're running version 1.25+ with QUIC compiled in and remember that UDP port 443 must be open in addition to TCP 443. Browser support is now near-universal, making HTTP/3 a safe deployment choice.


