Mastering QUIC and HTTP/3 with Nginx: The Complete Guide
QUIC makes every stream independent at the transport layer, so one lost packet stalls only its own stream instead of all of them as in HTTP/2 over TCP. Nginx needs 1.25.0+ and UDP/443 open, or it falls back to HTTP/2 without telling you.

The web has relied on TCP for over four decades. But modern applications—with their demands for real-time interactivity, mobile connectivity, and instant page loads—have exposed TCP’s fundamental limitations. Enter QUIC and HTTP/3: a complete reimagining of web transport that abandons TCP in favor of UDP to deliver a faster, more secure, and resilient internet.
In this comprehensive guide, we’ll explore the history and architecture of QUIC, understand why it solves problems that TCP cannot, and walk through a complete production-ready Nginx configuration.
TL;DR — QUIC and HTTP/3 on Nginx
6 key points- QUIC runs over UDP, not TCP: HTTP/3 maps HTTP semantics onto QUIC, which is built on UDP and mandates TLS 1.3.
- Solves HOL blocking: QUIC streams are independent at the transport layer, so a lost packet only stalls its own stream — not all multiplexed streams as in HTTP/2 over TCP.
- Faster handshakes and connection migration: combined transport+crypto handshake (1-RTT, or 0-RTT on resume) plus Connection IDs that survive network changes (Wi-Fi → mobile).
- Nginx needs 1.25.0+ with
--with-http_v3_moduleand a QUIC-capable SSL library (OpenSSL 3.5.1+, BoringSSL, QuicTLS, or LibreSSL 3.6+). - Core config: add
listen 443 quic reuseport;, enablehttp3 on, TLS 1.3, and theAlt-Svcheader;reuseportmust appear on exactly one listener per address:port. - Open UDP/443 on the firewall (server and cloud provider) or QUIC fails silently and clients fall back to HTTP/2; verify with
curl --http3, DevTools, orquic="h3"in the access log.
How I run this on my own infrastructure
jmrp.io serves HTTP/3 itself: both server blocks in production carry listen 443 quic; alongside the standard TCP listeners, so the site accepts QUIC and TLS-over-TCP connections side by side depending on what the client — and whatever sits between the client and the server — actually supports.
I didn’t take the Alt-Svc header’s word for it. Running curl --http3 straight against the live site returns HTTP/3 200: a completed QUIC handshake, not an inference drawn from a response header that a misconfigured server could still send while quietly falling back to HTTP/2 underneath. That distinction matters — plenty of “HTTP/3 enabled” checklists stop at the header and never confirm the protocol actually negotiated. It’s a small thing to verify, and it’s also the only way to know a QUIC deployment is real instead of aspirational: the header costs nothing to add, but only a client-side handshake proves UDP/443 is actually open and negotiating end to end.
Why did QUIC replace TCP for HTTP/3?
The QUIC protocol has an interesting evolution from a proprietary Google experiment to a full IETF standard.
| Year | Milestone | Significance |
|---|---|---|
| 2012 | Google begins QUIC development | Internal project to reduce web latency |
| 2013 | First QUIC traffic in Chrome | Early experiments with Google services |
| 2014 | Wide-scale gQUIC deployment | Chrome desktop uses QUIC for Google properties |
| 2016 | IETF QUIC Working Group formed | Formal standardization process begins |
| 2017 | IETF QUIC diverges from gQUIC | TLS 1.3 integration, general-purpose transport |
| 2021 | RFC 9000 published | QUIC Transport Protocol standardized |
| 2022 | RFC 9114 published | HTTP/3 standardized |
The QUIC RFC family
The complete QUIC specification spans multiple RFCs:
| RFC | Title | Description |
|---|---|---|
| RFC 9000 | QUIC Transport | Core protocol: packets, frames, streams, connection management |
| RFC 9001 | Using TLS to Secure QUIC | TLS 1.3 integration, key derivation, encryption levels |
| RFC 9002 | Loss Detection and Congestion Control | Packet loss recovery, RTT estimation, congestion algorithms |
| RFC 8999 | Version-Independent Properties | Behaviors common across all QUIC versions |
| RFC 9114 | HTTP/3 | HTTP semantics over QUIC |
Why QUIC? Understanding TCP’s limitations
To appreciate QUIC, we must understand why TCP—the backbone of the internet since 1974—struggles with modern web demands.
The head-of-line (HOL) blocking problem
This is the fundamental issue that QUIC solves. Imagine a highway with a single lane (TCP connection). If one car breaks down (packet loss), everyone behind must stop and wait, even if they’re going to different destinations.
The handshake latency problem
Establishing a secure TCP connection requires multiple round trips:
| Protocol | New Connection | Resumed Connection | Total RTTs |
|---|---|---|---|
| TCP + TLS 1.2 | TCP handshake + TLS handshake | TCP + abbreviated TLS | 3 RTT → 2 RTT |
| TCP + TLS 1.3 | TCP handshake + TLS 1.3 | TCP + TLS 0-RTT | 2 RTT → 1 RTT |
| QUIC | Combined handshake | 0-RTT early data | 1 RTT → 0 RTT |
The connection migration problem
TCP connections are identified by a 4-tuple: (source IP, source port, destination IP, destination port). When you switch from Wi-Fi to mobile data, your IP changes—and your TCP connection breaks.
QUIC architecture deep dive
Transport over UDP
QUIC is built on UDP rather than creating a new IP protocol. This was a pragmatic choice:
- No kernel changes required: UDP is universally supported
- Userspace implementation: Faster iteration and deployment
- Middlebox traversal: UDP passes through most NATs and firewalls
Packet structure
QUIC uses two packet types:
| Type | Header | Used For | Encryption |
|---|---|---|---|
| Long Header | Full connection info | Initial, Handshake, 0-RTT, Retry | Level-specific keys |
| Short Header | Minimal (post-handshake) | Application data (1-RTT) | Application keys |
Streams and flow control
QUIC streams are lightweight, multiplexed channels within a connection:
Stream ID Decoding: The Stream ID is a 62-bit integer where the least significant 2 bits function as a type header:
- Bit 0 (Initiator):
0= Client,1= Server - Bit 1 (Direction):
0= Bidirectional,1= Unidirectional
This creates 4 distinct address spaces:
- …00: Client Request/Response
- …01: Server Push (Deprecated)
- …10: Client Control Stream
- …11: Server Control Stream (QPACK)
Dual-Layer Flow Control: For a packet to be processed, it must pass two independent checks:
- Connection-Level: Is there
MAX_DATAcredit for the entire connection? - Stream-Level: Is there
MAX_STREAM_DATAcredit for this specific stream? If either is exhausted, transmission blocks until aWINDOW_UPDATEframe arrives.
The QUIC handshake: speed meets security
QUIC combines transport and cryptographic handshakes into a single exchange, dramatically reducing latency.
1-RTT handshake (first connection)
0-RTT handshake (resumed connection)
For clients that have connected before, QUIC allows sending encrypted data in the very first packet:
Security architecture
QUIC’s security is not optional—it’s built into the protocol from the ground up.
Four encryption levels
| Level | Keys Derived From | Used For | Forward Secrecy |
|---|---|---|---|
| Initial | Destination Connection ID | First packets, version negotiation | No |
| 0-RTT | Pre-shared key (PSK) | Early application data | No |
| Handshake | TLS handshake secrets | Handshake completion | Partial |
| Application (1-RTT) | TLS traffic secrets | All post-handshake data | Full (ECDHE) |
Packet protection
Every QUIC packet (except Initial) is protected with AEAD encryption:
| Packet Section | Component | Protection Level |
|---|---|---|
| Header | Flags | Protected (Header Protection) |
| Connection ID | Cleartext (for routing) | |
| Packet Number | Encrypted (Header Protection) | |
| Payload | Application Data | Fully Encrypted (AEAD) |
| Footer | Authentication Tag | 16-byte Integrity MAC |
Connection ID and privacy
DoS protection mechanisms
QUIC includes several anti-amplification and anti-spoofing measures:
- Address Validation: Servers send
RETRYpackets to validate client addresses - Anti-Amplification: Servers limit data sent before address validation (3x client data)
- Stateless Reset: Clean connection termination without state
HTTP/3: HTTP over QUIC
HTTP/3 is the mapping of HTTP semantics onto QUIC transport. It replaces HTTP/2’s binary framing with QUIC streams.
| Feature | HTTP/2 | HTTP/3 |
|---|---|---|
| Transport | TCP + TLS | QUIC (UDP + TLS 1.3) |
| Multiplexing | Stream frames in single connection | Native QUIC streams |
| HOL Blocking | Yes (at TCP layer) | No (independent streams) |
| Header Compression | HPACK | QPACK (handles out-of-order) |
| Server Push | Supported | Deprecated (rarely used) |
| Connection Migration | No | Yes |
Current adoption
As of 2026, HTTP/3 adoption is significant and growing:
| Metric | Value | Source |
|---|---|---|
| Websites using HTTP/3 | 36.6% | W3Techs (2026) |
| Chrome QUIC usage (subsequent) | 40% | APNIC Labs |
| Page load improvement (global) | 12.4% | Cloudflare Benchmarks |
| High-latency region improvement | 13.8% | DebugBear |
Nginx configuration: complete guide
Now let’s implement HTTP/3 on Nginx. This section covers everything from prerequisites to production deployment.
Prerequisites
Verify your Nginx installation:
nginx version: nginx/1.28.0 built with OpenSSL 3.5.0 8 Apr 2025 (running with OpenSSL 3.5.4 30 Sep 2025) configure arguments: … —with-http_v3_module …
How to install Nginx with HTTP/3 support
This repository (widely used for PHP and Nginx) provides the latest mainline versions with HTTP/3 support and many additional modules for Debian and Ubuntu.
# For Debian:
sudo apt install curl gpg
curl -fsSL https://packages.sury.org/nginx/README.txt | sudo bash -x
# For Ubuntu:
sudo add-apt-repository ppa:ondrej/nginx-mainline
sudo apt update
# Install Nginx:
sudo apt install nginx# Add official Nginx mainline repository
sudo apt install curl gnupg2 ca-certificates lsb-release
# Create keyring directory if it doesn't exist
sudo mkdir -p /etc/apt/keyrings
# Download and add the Nginx signing key (modern approach)
curl -fsSL https://nginx.org/keys/nginx_signing.key \
| sudo gpg --dearmor -o /etc/apt/keyrings/nginx.gpg
# Add repository with signed-by keyring
echo "deb [signed-by=/etc/apt/keyrings/nginx.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# Download Nginx and QuicTLS
wget https://nginx.org/download/nginx-1.25.3.tar.gz
git clone --depth 1 https://github.com/quictls/openssl quictls
# Build QuicTLS
cd quictls
./Configure --prefix=$PWD/build linux-x86_64
make -j$(nproc)
make install_sw
cd ..
# Configure and build Nginx
tar xzf nginx-1.25.3.tar.gz
cd nginx-1.25.3
./configure \
--with-http_v3_module \
--with-http_ssl_module \
--with-http_v2_module \
--with-cc-opt="-I../quictls/build/include" \
--with-ld-opt="-L../quictls/build/lib"
make -j$(nproc)
sudo make installBasic configuration
Here’s the minimal configuration to enable HTTP/3:
server {
server_name example.com;
root /var/www/example.com;
# ==========================================
# LISTENERS: TCP (HTTP/1.1, HTTP/2) + UDP (HTTP/3)
# ==========================================
# Standard HTTPS over TCP
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
# QUIC/HTTP/3 over UDP
# 'reuseport' is critical for multi-worker performance
listen 443 quic reuseport;
listen [::]:443 quic reuseport;
# ==========================================
# SSL/TLS CONFIGURATION
# ==========================================
# TLS 1.3 is REQUIRED for QUIC
ssl_protocols TLSv1.3 TLSv1.2;
ssl_prefer_server_ciphers off;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# ==========================================
# HTTP/3 CONFIGURATION
# ==========================================
http3 on;
# Advertise HTTP/3 support to browsers
# ma=86400 means "cache this info for 24 hours"
add_header Alt-Svc 'h3=":443"; ma=86400' always;
location / {
try_files $uri $uri/ =404;
}
}Complete production configuration
Here’s a comprehensive configuration with all optimizations. The configuration is split into two files: the main nginx.conf for global settings and a site-specific configuration file.
/etc/nginx/nginx.conf
# Main context
worker_processes auto;
error_log /var/log/nginx/error.log warn;
events {
worker_connections 4096;
use epoll;
multi_accept on;
}
http {
# ==========================================
# HTTP/3 GLOBAL SETTINGS
# ==========================================
# Explicit for clarity (http3 defaults to 'on' in nginx 1.25.0+)
http3 on;
# Custom log format to track HTTP/3 connections
log_format quic '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'proto="$server_protocol" quic="$http3"';
# MIME types
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Performance optimizations
sendfile on;
tcp_nopush on;
tcp_nodelay on;
# Gzip compression
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css application/json application/javascript
text/xml application/xml application/xml+rss text/javascript;
# Include site configurations
include /etc/nginx/sites-enabled/*;
}The site-specific configuration includes all the directives for QUIC, TLS, headers, and logging:
/etc/nginx/sites-available/example.com.conf
server {
server_name example.com www.example.com;
root /var/www/example.com;
# ==========================================
# DUAL-STACK LISTENERS
# ==========================================
# TCP: HTTP/1.1 and HTTP/2 fallback
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
# UDP: QUIC/HTTP/3
listen 443 quic reuseport;
listen [::]:443 quic reuseport;
# ==========================================
# TLS CONFIGURATION (Required for QUIC)
# ==========================================
ssl_protocols TLSv1.3 TLSv1.2;
# TLS 1.3 ciphersuites (handled separately for better OpenSSL compatibility)
ssl_conf_command Ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256;
# TLS 1.2 ciphers only (ECDHE for forward secrecy)
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;
ssl_prefer_server_ciphers off;
ssl_ecdh_curve X25519:P-256:P-384;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# Session resumption for performance
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off; # Better security
# OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;
# ==========================================
# QUIC-SPECIFIC SETTINGS
# ==========================================
# Enable 0-RTT early data (with security considerations)
ssl_early_data on;
# DoS protection: require address validation
quic_retry on;
# Performance: Generic Segmentation Offload (Linux 4.18+)
quic_gso on;
# ==========================================
# HEADERS
# ==========================================
# Advertise HTTP/3 availability
add_header Alt-Svc 'h3=":443"; ma=86400' always;
# Warn backends about 0-RTT replay risk
add_header Early-Data $ssl_early_data always;
# Security headers
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
# ==========================================
# LOGGING
# ==========================================
access_log /var/log/nginx/example.com.access.log quic;
error_log /var/log/nginx/example.com.error.log;
# ==========================================
# LOCATIONS
# ==========================================
location / {
try_files $uri $uri/ =404;
}
# Static assets with long cache
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
expires 1y;
add_header Cache-Control "public, immutable" always;
add_header Alt-Svc 'h3=":443"; ma=86400' always;
# Security headers (inherited from server block may not apply with add_header in location)
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
}
}
# HTTP to HTTPS redirect
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
return 301 https://$host$request_uri;
}All QUIC directives reference
| Directive | Default | Context | Description |
|---|---|---|---|
http3 | on | http, server | Enables HTTP/3 protocol negotiation |
http3_hq | off | http, server | Enables HTTP/0.9 over QUIC (testing only) |
http3_max_concurrent_streams | 128 | http, server | Maximum concurrent streams per connection |
http3_stream_buffer_size | 64k | http, server | Buffer size for reading/writing streams |
quic_active_connection_id_limit | 2 | http, server | Max connection IDs stored per connection |
quic_bpf | off | main | eBPF routing for connection migration (Linux 5.7+) |
quic_gso | off | http, server | Generic Segmentation Offload (Linux 4.18+) |
quic_host_key | - | http, server | File with secret key for address validation tokens |
quic_retry | off | http, server | Enables address validation via Retry packets |
Firewall configuration
Critical: HTTP/3 uses UDP on port 443, not TCP. If your firewall only allows TCP/443, QUIC will fail silently and clients will fall back to HTTP/2.
# Check current rules
sudo ufw status
# Allow UDP on port 443
sudo ufw allow 443/udp comment 'QUIC/HTTP3'
# Verify
sudo ufw status verbose# Allow incoming UDP 443
sudo iptables -A INPUT -p udp --dport 443 -j ACCEPT
# Save rules (Debian/Ubuntu)
sudo iptables-save | sudo tee /etc/iptables/rules.v4
# For IPv6
sudo ip6tables -A INPUT -p udp --dport 443 -j ACCEPT
sudo ip6tables-save | sudo tee /etc/iptables/rules.v6# Add UDP 443 to default zone
sudo firewall-cmd --permanent --add-port=443/udp
# Reload
sudo firewall-cmd --reload
# Verify
sudo firewall-cmd --list-portsAdd an inbound rule:
- Type: Custom UDP
- Port Range: 443
- Source: 0.0.0.0/0 (or your CIDR)
- Description: QUIC/HTTP3
Kernel tuning for high traffic
For high-traffic servers, increase UDP buffer sizes:
# Increase UDP buffer sizes for QUIC
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.core.rmem_default = 1048576
net.core.wmem_default = 1048576
# UDP memory limits
net.ipv4.udp_mem = 65536 131072 262144
net.ipv4.udp_rmem_min = 8192
net.ipv4.udp_wmem_min = 8192
# Allow more local ports for connections
net.ipv4.ip_local_port_range = 1024 65535Apply the settings:
Verification and testing
After configuration, verify HTTP/3 is working correctly.
Method 1: curl
Modern curl (7.66+ with HTTP/3 support) can test directly:
HTTP/3 200 server: jmrp.io date: Wed, 14 Jan 2026 20:02:32 GMT content-type: text/html; charset=utf-8 alt-svc: h3=“:443”; ma=86400 strict-transport-security: max-age=63072000; includeSubDomains; preload
Method 2: browser DevTools
- Open Chrome or Firefox
- Navigate to your site
- Open DevTools (F12) → Network tab
- Right-click column headers → Enable Protocol column
- Reload the page
- Look for
h3in the Protocol column
Method 3: online tools
| Tool | URL | Features |
|---|---|---|
| HTTP/3 Check | http3check.net | Quick pass/fail test with details |
| Cloudflare HTTP/3 Test | cloudflare-quic.com | Live connection test |
| Qualys SSL Labs | ssllabs.com | Comprehensive TLS analysis |
Method 4: check Nginx logs
Use the custom log format to verify HTTP/3 traffic:
192.168.1.100 - - [14/Jan/2026:20:02:32 +0000] “GET / HTTP/3” 200 15234 ”-” “Mozilla/5.0…” proto=“HTTP/3” quic=“h3”
Troubleshooting guide
| Symptom | Likely Cause | Solution |
|---|---|---|
| Protocol stays HTTP/2 | Firewall blocking UDP/443 | Open UDP port 443 on server and cloud provider |
| Protocol stays HTTP/2 | Missing Alt-Svc header | Add add_header Alt-Svc ‘h3=“:443”; ma=86400’ always; |
| Connection errors | TLS 1.3 not enabled | Enable TLSv1.3 for QUIC (required); TLSv1.2 may remain enabled for HTTP/2 fallback |
| Browsers refuse to use QUIC | Self-signed certificate | Use a valid certificate (Let’s Encrypt) |
| Nginx fails to start | Missing —with-http_v3_module | Rebuild Nginx with HTTP/3 module |
| High CPU usage | GSO not supported by kernel | Disable quic_gso or upgrade kernel |
| 0-RTT not working | OpenSSL version too old | Use OpenSSL 3.5.1+, QuicTLS, or BoringSSL |
Debug mode
Enable debug logging temporarily:
error_log /var/log/nginx/error.log debug;Check for QUIC-specific messages:
2026/01/14 10:42:15 [debug] 12345#0: *1 quic handle packet: fd:16, addr:192.0.2.1:54321 2026/01/14 10:42:15 [debug] 12345#0: *1 quic packet rx dcid len:8 87654321 2026/01/14 10:42:15 [debug] 12345#0: *1 quic packet rx scid len:8 12345678 2026/01/14 10:42:15 [info] 12345#0: *1 quic SSL_do_handshake() failed: SSL_ERROR_SSL: error:14094416:SSL routines:ssl3_read_bytes:sslv3 alert certificate unknown 2026/01/14 10:42:15 [debug] 12345#0: *1 quic close connection: 0:
Production deployment checklist
Before deploying HTTP/3 to production, verify:
| Category | Item | Verification |
|---|---|---|
| Build | Nginx compiled with —with-http_v3_module | nginx -V 2>&1 | grep http_v3 |
| Build | Compatible SSL library (QuicTLS/BoringSSL/OpenSSL 3.5.1+) | nginx -V 2>&1 | grep -i ssl |
| Network | UDP/443 open on server firewall | sudo ss -ulnp | grep 443 |
| Network | UDP/443 open on cloud provider (AWS/GCP/Azure) | Check security group/firewall rules |
| Config | listen 443 quic reuseport; present | nginx -T | grep quic |
| Config | TLS 1.3 enabled | nginx -T | grep ssl_protocols |
| Config | Alt-Svc header configured | curl -I https://site | grep alt-svc |
| Certificate | Valid (not self-signed) certificate | openssl s_client -connect site:443 |
| Test | HTTP/3 confirmed working | curl —http3 https://site |
| Monitoring | Log format includes $http3 | Check log format configuration |
When not to use QUIC
While QUIC offers significant benefits, there are scenarios where TCP may be preferable:
Frequently asked questions
What do I need to enable HTTP/3 in Nginx?
You need Nginx 1.25.0 or newer (HTTP/3 support was added mid-2023) compiled with the --with-http_v3_module, plus an SSL library with QUIC support such as OpenSSL 3.5.1+, BoringSSL, QuicTLS, or LibreSSL 3.6+. TLS 1.3 is required for QUIC.
Does HTTP/3 use TCP or UDP?
HTTP/3 runs over QUIC, which is built on UDP rather than TCP. You must open UDP port 443 on both the server firewall and any cloud provider firewall, or QUIC fails silently and clients fall back to HTTP/2.
How does QUIC solve TCP's head-of-line blocking?
TCP treats everything as one ordered byte stream, so a single lost packet blocks all multiplexed HTTP/2 streams until it is retransmitted. QUIC implements streams at the transport layer, giving each stream independent delivery, so a lost packet only affects its own stream.
Why does my browser still show HTTP/2 on the first visit?
This is normal. On the first visit browsers use HTTP/2 to discover the Alt-Svc header advertising HTTP/3; on subsequent requests or after a reload they switch to HTTP/3.
Why is the reuseport flag critical for QUIC in Nginx?
UDP is connectionless, so without reuseport all QUIC packets are handled by a single Nginx worker, creating a bottleneck. The reuseport kernel feature distributes packets across workers, but it must appear on exactly one listen directive per address:port globally or Nginx fails to start.
How do I verify HTTP/3 is working?
Use modern curl with curl -I --http3 https://yoursite, enable the Protocol column in browser DevTools and look for h3, use online tools like http3check.net or cloudflare-quic.com, or grep your Nginx access log for quic="h3".
Further Reading & Resources
- RFC 9000 IETF
- RFC 9001 IETF
- RFC 9002 IETF
- RFC 8999 IETF
- RFC 9114 IETF
- W3Techs (2026) w3techs.com
- APNIC Labs blog.apnic.net
- Cloudflare Benchmarks blog.cloudflare.com
- DebugBear debugbear.com
- http3 Nginx
- http3_hq Nginx
- http3_max_concurrent_streams Nginx
- http3_stream_buffer_size Nginx
- quic_active_connection_id_limit Nginx
- quic_bpf Nginx
- quic_gso Nginx
- quic_host_key Nginx
- quic_retry Nginx
- http3check.net http3check.net
- cloudflare-quic.com cloudflare-quic.com
- ssllabs.com Qualys SSL Labs