HTTP/3 replaces TCP+TLS with QUIC, claiming to solve “head-of-line blocking”. But how does it actually perform? This post uses simulated degraded networks to compare HTTP/2 and HTTP/3 with real data.
1. HTTP/3 Core Improvements
1.1 Protocol Stack Comparison
1
2
3
4
5
6
7
8
9
10
11
12
| HTTP/2: HTTP/3:
┌─────────────┐ ┌─────────────┐
│ HTTP/2 │ │ HTTP/3 │
├─────────────┤ ├─────────────┤
│ TLS 1.3 │ │ QUIC │ ← Transport + encryption merged
├─────────────┤ │ (incl TLS) │
│ TCP │ └──────┬──────┘
└─────────────┘ │
│
┌─────▼─────┐
│ UDP │
└───────────┘
|
1.2 Theoretical Advantages
| Problem | HTTP/2 (TCP) | HTTP/3 (QUIC) |
|---|
| Handshake latency | TCP (1-RTT) + TLS (1-2 RTT) | 0-1 RTT |
| Head-of-line blocking | Exists at TCP layer | Completely solved |
| Connection migration | IP change requires reconnect | Seamless migration |
| Packet loss recovery | Slow retransmission | Faster recovery |
2. Test Environment Setup
2.1 Simulating Degraded Networks
Using Linux tc command:
1
2
3
4
5
6
7
8
| # Add 100ms delay + 5% packet loss
sudo tc qdisc add dev eth0 root netem delay 100ms loss 5%
# Check current settings
tc qdisc show dev eth0
# Restore normal
sudo tc qdisc del dev eth0 root
|
2.2 Test Targets
Pick a site that supports both HTTP/2 and HTTP/3:
1
2
3
| # Check if site supports HTTP/3
curl -I https://cloudflare.com 2>&1 | grep -i alt-svc
# alt-svc: h3=":443" ← Supports HTTP/3
|
2.3 curl with HTTP/3 Support
1
2
3
4
5
6
7
| # Check curl version (need 7.66+)
curl --version | grep HTTP3
# If not supported, install HTTP/3-enabled version
# Ubuntu:
sudo add-apt-repository ppa:savoury1/curl34
sudo apt update && sudo apt install curl
|
3.1 Normal Network
1
2
3
4
5
| # HTTP/2
curl -w "Total: %{time_total}s\n" -o /dev/null -s --http2 https://cloudflare.com
# HTTP/3
curl -w "Total: %{time_total}s\n" -o /dev/null -s --http3 https://cloudflare.com
|
Results (no degradation):
| Protocol | First Connection | Reused Connection |
|---|
| HTTP/2 | 180ms | 50ms |
| HTTP/3 | 150ms | 45ms |
HTTP/3 is slightly faster, but not by much.
3.2 High Latency Network (100ms RTT)
1
2
3
4
5
6
7
8
9
10
| # Add delay
sudo tc qdisc add dev eth0 root netem delay 100ms
# Test
for i in {1..10}; do
echo "=== HTTP/2 ==="
curl -w "%{time_total}s\n" -o /dev/null -s --http2 https://cloudflare.com
echo "=== HTTP/3 ==="
curl -w "%{time_total}s\n" -o /dev/null -s --http3 https://cloudflare.com
done
|
Results (100ms RTT):
| Protocol | Avg Time | Handshake % |
|---|
| HTTP/2 | 650ms | 46% |
| HTTP/3 | 450ms | 33% |
Analysis: On high-latency networks, QUIC’s 0-RTT handshake advantage is significant.
3.3 Lossy Network (5% Packet Loss)
1
2
3
4
5
6
| # Add packet loss
sudo tc qdisc change dev eth0 root netem delay 50ms loss 5%
# Test (load a large page)
time curl -o /dev/null -s --http2 https://www.cloudflare.com/
time curl -o /dev/null -s --http3 https://www.cloudflare.com/
|
Results (5% packet loss):
| Protocol | Avg Time | Std Dev |
|---|
| HTTP/2 | 2.1s | ±0.8s |
| HTTP/3 | 1.3s | ±0.3s |
Key findings:
- HTTP/3 is 40% faster on average
- HTTP/3 is more stable (lower standard deviation)
4. Packet Analysis
4.1 Capturing QUIC with Wireshark
1
2
3
4
5
6
7
| # Need to set SSLKEYLOGFILE to decrypt QUIC
export SSLKEYLOGFILE=/tmp/keylog.txt
curl --http3 https://cloudflare.com
# In Wireshark:
# Edit → Preferences → Protocols → TLS
# Set (Pre)-Master-Secret log filename: /tmp/keylog.txt
|
4.2 Observing Head-of-Line Blocking
HTTP/2 (TCP) Problem:
1
2
3
4
5
6
7
8
9
| Timeline:
Stream 1: [Packet 1] [Packet 2] ...
Stream 2: [Packet 1] [Packet 2] ...
Stream 3: [Packet 1] [Packet 2] ...
If Stream 1's Packet 1 is lost:
TCP: Wait for retransmission... (ALL streams are blocked!)
↓
Stream 2, 3 data has arrived, but can't be delivered to app
|
HTTP/3 (QUIC) Solution:
1
2
3
4
| QUIC implements multiplexing over UDP:
Stream 1: [Packet 1 lost] → Retransmit
Stream 2: [Processing normally] ← Not affected!
Stream 3: [Processing normally] ← Not affected!
|
4.3 Connection Migration Demo
QUIC uses Connection ID instead of IP:Port to identify connections:
1
2
3
4
5
6
7
8
9
10
11
| # Simulate WiFi → 4G switch
# 1. Start download
curl --http3 -O https://terra-nas.com/largefile.bin &
# 2. Switch network interface
sudo ip route change default via 192.168.1.1 dev wlan0
sudo ip route change default via 10.0.0.1 dev eth0
# 3. Observe if download is interrupted
# HTTP/3: Continues (Connection ID unchanged)
# HTTP/2: Connection drops, needs to reconnect
|
5. QUIC Internals Deep Dive
5.1 Flow Control
QUIC has two levels of flow control:
1
2
3
4
5
6
7
8
9
10
11
| Connection level: Limits total flow across all Streams
Stream level: Limits individual Stream flow
┌─────────────────────────────────────┐
│ Connection Window = 1MB │
│ ┌───────────┐ ┌───────────┐ │
│ │ Stream 1 │ │ Stream 2 │ ... │
│ │ Window= │ │ Window= │ │
│ │ 256KB │ │ 256KB │ │
│ └───────────┘ └───────────┘ │
└─────────────────────────────────────┘
|
5.2 Packet Loss Detection & Recovery
1
2
3
4
5
6
7
8
9
10
11
| TCP packet loss recovery:
Send: 1, 2, 3, 4, 5
ACK: 1, 2, - (3 lost)
Wait for timeout... or 3 duplicate ACKs
Retransmit: 3
QUIC packet loss recovery:
Each packet has a unique, incrementing sequence number
ACK packets contain detailed received ranges
More accurate RTT estimation
Faster retransmission triggering
|
5.3 0-RTT Mechanism
1
2
3
4
5
6
7
8
| First connection:
Client → Server: ClientHello + supported keys
Client ← Server: ServerHello + cert + Session Ticket
Client → Server: Start sending encrypted data
Subsequent connections:
Client → Server: ClientHello + EarlyData (using previous Session Ticket)
Data is already transmitting! No need to wait for handshake
|
6. Practical Recommendations
6.1 When to Use HTTP/3
| Scenario | Recommendation | Reason |
|---|
| Mobile clients | Strongly recommended | Frequent network switching, high packet loss |
| Weak network regions | Strongly recommended | High latency, high packet loss |
| Internal services | Optional | Stable network, small benefit |
| Real-time communication | Recommended | Low latency requirements |
6.2 Nginx HTTP/3 Configuration
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| # Requires Nginx 1.25+ or nginx-quic
server {
listen 443 ssl;
listen 443 quic reuseport;
http2 on;
http3 on;
http3_hq on;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
# Tell clients HTTP/3 is supported
add_header Alt-Svc 'h3=":443"; ma=86400';
}
|
6.3 CDN Support Status
| CDN | HTTP/3 Support |
|---|
| Cloudflare | Enabled by default |
| Google Cloud CDN | Supported |
| AWS CloudFront | Supported |
| Akamai | Supported |
7. Summary
7.1 Test Conclusions
| Network Condition | HTTP/3 Advantage |
|---|
| Normal network | Slightly faster 10-20% |
| High latency (100ms+) | 30-40% faster |
| High packet loss (5%+) | 40%+ faster, more stable |
| Network switching | Seamless migration vs reconnect |
7.2 Key Takeaways
- Head-of-line blocking is a real problem: On lossy networks, TCP-multiplexed streams block each other
- 0-RTT is a killer feature: Noticeably better on high-latency networks
- UDP ≠ unreliable: QUIC implements reliable transport in userspace
- Low migration cost: Major browsers and CDNs already support it, just flip a config switch
Recommendation: If your user base has a significant mobile or weak-network population, HTTP/3 is worth enabling.