HTTP/3 & QUIC
HTTP/3 & QUIC
HTTP/2 was a major step forward. But it had one problem it couldn’t fix from inside the protocol: TCP itself. After six years of HTTP/2 deployment, Google’s traffic data showed something uncomfortable — on lossy networks, HTTP/2 was sometimes worse than HTTP/1.1. The fix required throwing out TCP as the foundation, which meant building an entirely new transport protocol. That’s QUIC.
HTTP/3 is just HTTP semantics — same methods, headers, status codes — running on top of QUIC instead of TCP.
Why HTTP/3 Exists
The TCP HOL Blocking Problem
HTTP/2’s headline feature was multiplexing: multiple streams over one TCP connection. No more opening 6 parallel connections per domain. This worked great on reliable networks. On lossy networks, it backfired.
TCP is a byte stream. It guarantees delivery and ordering across the entire connection. When a single packet is lost, TCP’s retransmit mechanism holds up all data until that packet arrives — even data belonging to completely unrelated streams.
So you have streams A, B, C, D multiplexed over one TCP connection. Packet carrying stream A data is lost. TCP pauses delivery of B, C, D while it retransmits for A. HTTP/2 has no idea — the multiplexing lives above TCP’s view of the world. All four streams stall.
With HTTP/1.1’s 6 separate connections, losing a packet on connection 1 only blocks connection 1. The other 5 keep moving. In some cases this is actually faster.
ELI5: HTTP/2 put everyone in one lane of traffic because it’s more efficient. But when there’s a crash blocking that lane, everyone is stuck. HTTP/1.1 used 6 lanes — inefficient, but a crash in lane 1 doesn’t stop lanes 2-6. HTTP/3 keeps the single lane but gives each passenger their own teleportation in case of emergency.
QUIC’s Core Insight
Move reliability and ordering down to the stream level, not the connection level. QUIC is built on UDP — which provides no ordering, no reliability, no flow control. QUIC re-implements all of that, but per-stream. Lost packet on stream A? Stream A pauses. Streams B, C, D keep flowing.
Timeline
| Year | Event |
|---|---|
| 2012 | Jim Roskind at Google starts experimenting with QUIC |
| 2013 | Google deploys QUIC experiment to Chrome + Google servers |
| 2015 | Google publishes QUIC spec, IETF starts standardization |
| 2018 | IETF draft named “HTTP/3” (previously HTTP-over-QUIC) |
| 2021 | RFC 9000 (QUIC) and RFC 9114 (HTTP/3) published |
| 2022+ | Major CDNs and browsers ship HTTP/3 by default |
Google’s internal data before RFC: QUIC reduced search latency by ~8% globally, YouTube rebuffer rate by ~18% on mobile. Those numbers drove the IETF standardization effort.
QUIC Protocol Deep Dive
Built on UDP, Not TCP
QUIC is a transport protocol that lives in user space. It runs over UDP (port 443 typically), so it can evolve without OS kernel changes. This is significant — TCP is baked into every OS kernel, and changing it requires coordination across billions of devices. QUIC can be updated by shipping a new version of Chrome or nginx.
Application (HTTP/3)
|
QUIC
|
UDP
|
IP
QUIC provides everything TCP provides plus more:
- Reliable delivery (per stream)
- Ordered delivery (per stream)
- Flow control (per stream AND per connection)
- Congestion control (Cubic, BBR, etc.)
- Multiplexing (native, not bolted on)
- Connection migration
- Mandatory encryption (TLS 1.3, always)
Connection IDs
TCP identifies a connection by the 4-tuple: source IP, source port, destination IP, destination port. Change any of these and the connection breaks.
QUIC uses Connection IDs (CIDs) — opaque tokens agreed upon during handshake. The network path can change completely; as long as both sides recognize the CID, the connection survives.
ELI5: TCP is like a phone call on a landline — if you unplug and replug the cable, the call drops. QUIC is like a WhatsApp call — you can switch from WiFi to cellular and the call keeps going because it’s identified by your account, not the wire.
Mandatory TLS 1.3
In TCP, TLS is optional — you can run HTTP over plain TCP. In QUIC, TLS 1.3 is mandatory and integrated into the QUIC handshake itself. There is no “unencrypted QUIC.”
This isn’t just a security policy. QUIC’s packet headers (except the outermost bytes) are encrypted. Middleboxes — firewalls, load balancers, transparent proxies — can’t inspect or modify QUIC internals the way they can with TCP. This prevents ossification: the QUIC ecosystem can evolve without middleboxes breaking new features.
Common mistake: “HTTP/3 is just HTTP/2 with encryption.” No. HTTP/2 has optional TLS (HTTPS). HTTP/3 always uses TLS 1.3. They’re architecturally different in this regard.
Stream Independence
This is the central win of HTTP/3 over HTTP/2. Worth understanding precisely.
HTTP/2: Multiplexing Over One TCP Stream
TCP Connection
├── Stream 1: GET /style.css
├── Stream 2: GET /app.js
├── Stream 3: GET /image.png
└── Stream 4: GET /font.woff2
Packet loss affects Stream 3:
┌──────────────────────────────┐
│ TCP holds ALL stream data │
│ until Stream 3 packet arrives│
│ Streams 1, 2, 4 are frozen │
└──────────────────────────────┘
HTTP/3: Independent QUIC Streams
QUIC Connection
├── Stream 1: GET /style.css ─── independent flow control
├── Stream 2: GET /app.js ─── independent loss recovery
├── Stream 3: GET /image.png ─── packet loss here...
└── Stream 4: GET /font.woff2 ─── ...doesn't affect these
Packet loss on Stream 3:
┌──────────────────────────────┐
│ Stream 3 retransmits only │
│ Streams 1, 2, 4 keep flowing │
└──────────────────────────────┘
ELI5: HTTP/2 multiplexing over TCP is like a multi-lane highway that merges into one lane right before a bottleneck. HTTP/3 streams are like separate roads that never merge — an accident on road 3 doesn’t affect roads 1, 2, 4.
Real-World Impact
The improvement is biggest on lossy networks. On a 1% packet loss rate (common on mobile/WiFi):
- HTTP/1.1: 6 connections, each occasionally hit by loss
- HTTP/2: 1 connection, one loss event blocks all streams
- HTTP/3: streams recover independently
Studies from Cloudflare and Google show HTTP/3 load times 15-25% faster at 2% packet loss, and the gap grows with loss rate. On wired data center links with <0.01% loss, the difference is negligible.
Connection Migration
TCP’s 4-tuple binding causes real problems for mobile users. Every time you switch networks — WiFi to cellular, handoff between access points — your IP changes. The TCP connection dies. The application must reconnect, redo TLS, restart any in-progress transfers.
How QUIC Migration Works
Initial connection (WiFi):
Client [192.168.1.5:54321] ──CID:abc123──> Server [1.2.3.4:443]
Network change (cellular):
Client [10.0.0.5:58900] ──CID:abc123──> Server [1.2.3.4:443]
Server sees: same CID, different path → migration
┌─────────────────────────────────────────┐
│ Server responds on new path │
│ Connection continues without restart │
│ In-flight data resumes │
└─────────────────────────────────────────┘
QUIC also handles NAT rebinding silently — when your home router’s NAT table refreshes and assigns a different external port, QUIC keeps the connection via CID. TCP would die.
Practical impact: A user streaming video or in a long API request survives network transitions without the application layer noticing. This matters for mobile-first services — maps, streaming, real-time collaboration.
Common mistake: Connection migration doesn’t mean you can migrate to a completely different server. The CID is scoped to a server (or server cluster with shared state). It handles client-side path changes only.
0-RTT and 1-RTT Handshakes
The connection setup cost is one of the most visible latency differences between protocol generations.
Handshake Comparison
HTTP/1.1 + TLS 1.2 (3 RTTs before first byte of data):
Client Server
│──── TCP SYN ────────────────>│ ─┐
│<─── TCP SYN-ACK ─────────────│ │ RTT 1: TCP
│──── TCP ACK ────────────────>│ ─┘
│──── TLS ClientHello ────────>│ ─┐
│<─── TLS ServerHello+Cert ────│ │ RTT 2: TLS
│──── TLS Finished ───────────>│ ─┘
│──── HTTP GET ───────────────>│ ─┐
│<─── HTTP Response ───────────│ │ RTT 3: Data
─┘
HTTP/2 + TLS 1.3 (2 RTTs):
RTT 1: TCP handshake
RTT 2: TLS 1.3 handshake (1-RTT TLS)
RTT 3: First request
QUIC new connection (1 RTT):
RTT 1: Combined QUIC+TLS 1.3 handshake
RTT 2: First request data arrives
(In practice: QUIC sends HTTP request optimistically at end of handshake packet)
QUIC resumed connection (0-RTT):
Client sends HTTP request IN THE FIRST PACKET
Server responds without waiting for handshake completion
| Scenario | TCP+TLS 1.2 | TCP+TLS 1.3 | QUIC new | QUIC resumed |
|---|---|---|---|---|
| RTTs before data | 3 | 2 | 1 | 0 |
| Absolute latency @ 50ms RTT | 150ms | 100ms | 50ms | ~0ms |
0-RTT Replay Risk
0-RTT is powerful but has a real security constraint. Session resumption data (PSK) can be captured by an attacker and replayed — sending the same “first packet” again later. If that packet contains a POST /checkout or DELETE /resource, you have a replay attack.
The spec recommends 0-RTT only for GET requests (idempotent). Servers should reject or handle non-idempotent 0-RTT requests carefully. Well-implemented servers (nginx, Caddy) do this by default.
ELI5: 0-RTT is like having a secret handshake memorized from your last meeting. You can skip the greeting and start talking immediately. But if someone records your secret handshake and uses it later, they can pretend to be you. So you only use it for “public” conversations (GET requests), not “private” ones (POST/DELETE).
Common mistake: Enabling 0-RTT without accounting for non-idempotent endpoints. “Replay safe” doesn’t mean “replay impossible” — it means your server handles replays correctly, which requires explicit implementation.
QPACK — Header Compression for HTTP/3
HTTP/2 uses HPACK for header compression. HPACK maintains a dynamic table — both sides keep a synchronized list of previously seen header name-value pairs and reference them by index. This saves significant bandwidth on repeated headers (Authorization, Content-Type, User-Agent appear on every request).
Why HPACK Doesn’t Work With QUIC
HPACK requires strict ordering. If stream 1 sends “add entry 62 to table” and stream 2 references entry 62, stream 2 must be processed after stream 1. This creates head-of-line blocking at the header compression layer — the opposite of what QUIC is trying to achieve.
QPACK’s Design
QPACK introduces three streams:
- Request streams — carry actual HTTP requests/responses (many, bidirectional)
- Encoder stream — client → server, carries dynamic table updates (one, unidirectional)
- Decoder stream — server → client, carries acknowledgments (one, unidirectional)
Client Server
│──── Encoder stream (updates) ──>│
│<─── Decoder stream (acks) ──────│
│═══════════════════════════════ │
│──── Request stream 1 ──────────>│
│──── Request stream 2 ──────────>│ (can reference table entries
│──── Request stream 3 ──────────>│ independently)
│<─── Response stream 1 ──────────│
│<─── Response stream 3 ──────────│ (stream 2 still in flight, doesn't block)
Request streams can either:
- Use static table entries (safe, no dependencies)
- Use dynamic table entries (better compression, requires encoder ack)
- Skip dynamic table entirely (worst compression, safest)
QPACK’s static table has 99 entries vs HPACK’s 61 — expanded to cover modern common headers (:authority, content-encoding: br, etc.).
ELI5: HPACK compression is like a shared whiteboard — both sides write and reference the same list. But if you can’t guarantee who reads the whiteboard first (QUIC streams are independent), it breaks. QPACK gives the whiteboard its own dedicated communication channel that’s ordered, while letting everyone else work in any order they want.
The trade-off: QPACK is slightly less efficient than HPACK on compressible headers because it’s more conservative about dynamic table use. In practice, the difference is small — compression ratios are similar.
HTTP/3 in Practice
Server Support
| Server | HTTP/3 Support |
|---|---|
| Caddy | Built-in, enabled by default |
| nginx | Via patch (quiche) or nginx-quic branch |
| LiteSpeed | Native support |
| Cloudflare | All traffic via QUIC |
| AWS CloudFront | GA since 2022 |
| Fastly | Supported |
| Apache | Not yet (as of 2025) |
Browser Support
All major browsers support HTTP/3: Chrome, Firefox, Safari, Edge. They all implement IETF QUIC (RFC 9000), not Google’s original gQUIC.
Alt-Svc Header: The Discovery Mechanism
Browsers don’t know which protocol a server supports until they connect. The bootstrap is always HTTP/2 or HTTP/1.1 over TCP. Servers advertise HTTP/3 capability via the Alt-Svc response header:
Alt-Svc: h3=":443"; ma=86400
This says: “I support HTTP/3 (h3) on port 443. Cache this for 86400 seconds.” Next request, browser tries QUIC first. If it works, it uses it. If not (UDP blocked), falls back to TCP.
Some CDNs also use DNS HTTPS records to advertise HTTP/3 capability without needing the initial TCP connection.
The UDP Blocking Problem
QUIC runs on UDP. Many corporate firewalls, NAT devices, and middleboxes either block UDP on port 443 or deprioritize it. QUIC even has an explicit fallback mechanism (RFC 9000 §22.2) — if QUIC handshake doesn’t complete in a timeout, browsers fall back to TCP+TLS.
Estimated UDP blocking rate: 5-15% of enterprise networks, much lower on consumer networks. This is the main deployment friction for HTTP/3.
Common mistake: “QUIC doesn’t work in enterprise environments.” Partially true — but the fallback to HTTP/2 is automatic and transparent. Users don’t see errors; they just don’t get the QUIC performance benefit.
When Does HTTP/3 Actually Help?
| Scenario | Impact | Why |
|---|---|---|
| Mobile users (LTE, 5G) | High | Lossy links, connection migration |
| International / high latency | High | Fewer RTTs for connection setup |
| WiFi in crowded spaces | High | Packet loss common |
| Video streaming | Medium-High | Migration, lower rebuffering |
| Data center to data center | Low | Reliable links, latency already low |
| Single large file download | Low | No multiplexing benefit, one stream |
| API calls (small payloads) | Medium | 0-RTT matters more than loss recovery |
ELI5: HTTP/3 is most valuable in the same conditions where TCP suffers most — unreliable networks, high latency, network changes. It’s not magic for all use cases. Deploying it in a data center won’t move your metrics.
The Bigger Picture: QUIC Beyond HTTP
QUIC is a general-purpose transport. HTTP/3 is just one application.
WebTransport — bidirectional, unreliable or reliable streams from the browser, built on QUIC. Replaces WebSockets for real-time games, live video, collaborative apps. Supports datagrams (UDP-like, best-effort) alongside streams.
MASQUE — tunneling protocol over QUIC. Replaces traditional VPN and proxy protocols. HTTP/3 CONNECT upgrade, QUIC connection tunneled inside QUIC.
gRPC over HTTP/3 — gRPC currently runs on HTTP/2+TCP. Moving to HTTP/3 removes the per-connection HOL blocking that limits microservice throughput. Adoption is still early (2024-2025).
DNS over QUIC (DoQ) — RFC 9250. DNS queries over QUIC. Lower latency than DNS-over-TCP, better than DNS-over-HTTPS for pure DNS traffic.
QUIC’s user-space implementation means the protocol stack can be updated by deploying new app code. This is the “ossification” prevention strategy — TCP couldn’t evolve because it’s kernel-space and middleboxes learned to exploit its structure.
Protocol Comparison Summary
| Feature | HTTP/1.1 | HTTP/2 | HTTP/3 |
|---|---|---|---|
| Transport | TCP | TCP | QUIC (UDP) |
| Multiplexing | No (6 connections) | Yes (one TCP conn) | Yes (one QUIC conn) |
| HOL blocking | Yes (per-conn) | Yes (TCP-level) | No (stream-independent) |
| Header compression | None | HPACK | QPACK |
| Connection setup | TCP+TLS: 2-3 RTTs | TCP+TLS: 2 RTTs | QUIC: 1 RTT / 0-RTT |
| Encryption | Optional (HTTPS) | Optional (HTTPS) | Mandatory (TLS 1.3) |
| Connection migration | No | No | Yes (Connection IDs) |
| Prioritization | Hacky (workarounds) | Binary framing + priority | QUIC priorities |
| Server push | No | Yes (rarely used) | Yes (deprecated in practice) |
| Browser support | Universal | Universal | All major browsers |
| UDP blocking risk | N/A | N/A | 5-15% enterprise |
| Best for | Simple/legacy | Most web traffic | Lossy/mobile/global |
What to Know at Each Level
Mid-level: HTTP/3 uses QUIC instead of TCP. QUIC is UDP-based. The main benefit is stream independence — lost packets don’t block other streams. Connection migration helps mobile. 0-RTT resumes connections without handshake overhead.
Senior: Understand QPACK vs HPACK design difference and why it matters for ordered/unordered delivery. Know the Alt-Svc discovery mechanism. Know when HTTP/3 helps vs doesn’t. Can debug QUIC connection issues (UDP blocked, handshake timeout, fallback).
Principal: Evaluate QUIC deployment against your specific traffic profile (loss rate, latency distribution, mobile vs data center ratio). Understand QUIC congestion control options (Cubic, BBR, RENO) and how they interact with multi-stream environments. Know the roadmap: WebTransport, MASQUE, QUIC v2 (RFC 9369). Can reason about 0-RTT security surface area in your API design.