# QUIC and HTTP/3: Rebuilding TCP in Userspace, on Purpose

> HTTP/2 fixed application-layer head-of-line blocking and exposed the transport-layer version underneath. QUIC's answer: reimplement reliability per-stream over UDP, encrypt the transport headers, and make connections survive IP changes.

URL: https://harshith.in/blog/quic-and-http3/
Author: Harshith Sunku (Staff Engineer) — https://harshith.in/about/
Tags: networking, performance, web

HTTP/2 multiplexed many requests onto one TCP connection and declared
victory over head-of-line blocking — then production traffic on lossy
networks revealed the fine print. One lost TCP segment stalls *every*
stream on the connection, because TCP guarantees a single ordered byte
stream and will not release byte N+1 until byte N arrives. The streams
were independent in HTTP's imagination and serialized in TCP's reality.
QUIC (RFC 9000, 2021) is the industry's considered response: if the
transport's ordering model is the problem, replace the transport.

## Why UDP, and What Got Rebuilt

QUIC runs over UDP not because UDP is good but because it's *available*:
a new IP protocol number would be dropped by a decade of middleboxes
that only understand TCP and UDP (the ossification lesson — SCTP solved
much of this in 2000 and never made it across the public internet). Over
that thin UDP shim, QUIC rebuilds everything TCP provided — but with the
loss-recovery unit changed:

```text
  TCP+HTTP/2:  streams A,B,C → one byte stream → packet with A-bytes lost
               ⇒ B and C stall behind retransmission (transport HoL)

  QUIC/HTTP/3: streams A,B,C each independently ordered
               packet with A-frames lost ⇒ A waits; B, C deliver
```

Streams are first-class transport objects: per-stream ordering, per-stream
flow control, connection-level congestion control shared across them
(loss detection is richer than TCP's, too — monotonic packet numbers
kill retransmission ambiguity, ACK ranges beat SACK). Packet loss now
costs the streams whose frames were in the lost packet, nothing else.
The benefit is real and measurable precisely where networks are bad —
which is to say, on the mobile internet where most traffic lives; on
clean links, TCP+H2 and H3 are near-identical, which keeps the benchmark
wars evergreen.

## Encryption as Armor Against Middleboxes

QUIC integrates TLS 1.3 into the transport handshake — key exchange and
transport parameters travel together, so a cold connection completes in
**1 RTT** (vs TCP's SYN round trip *then* TLS) and resumption offers the
same 0-RTT-with-replay-hazards as TLS 1.3 elsewhere. But the deeper
decision: QUIC encrypts *almost the entire transport layer* — packet
numbers, ACKs, connection state, all opaque; only a minimal invariant
header stays visible. That's not just privacy — it's a deliberate
anti-ossification weapon: what middleboxes cannot parse, they cannot
"helpfully" normalize, and the protocol stays evolvable (the spec even
greases unused codepoints to keep boxes honest). The trade is real,
though: operators lost passive RTT/loss observability overnight — the
*spin bit* exists as a negotiated crumb for them — and stateful
firewalls see only "UDP flows," which is why some enterprises still
block UDP/443 and browsers silently fall back to TCP (every H3 client
carries a full H2 stack as plan B; discovery via `Alt-Svc` and HTTPS
DNS records makes H3 opt-in per connection).

Two more consequences of owning the transport. **Connection migration**:
QUIC connections are identified by connection IDs, not 4-tuples — walk
from Wi-Fi to LTE and the connection *continues* through the IP change,
no re-handshake; NAT rebinding stops being fatal too. And **userspace
implementation**: QUIC stacks live in the application (quiche in
Chrome/Cloudflare, msquic, quic-go...), so transport improvements ship
with an app update instead of a kernel upgrade — the same agility that
let Google iterate gQUIC into standardization. The bill: UDP paths are
less optimized in kernels/NICs than decades-tuned TCP (GSO/GRO for UDP,
crypto offload are catching up), so QUIC burns measurably more CPU per
byte at the server — a real scaling consideration at CDN volumes, and
the reason "just enable H3" is a capacity question, not a checkbox.

## Takeaways

- H2 moved head-of-line blocking down a layer; QUIC removes it by making
  streams the unit of ordering and loss recovery.
- UDP is a compatibility shim; encrypted transport headers are armor so
  the *next* protocol change doesn't take 20 years.
- 1-RTT cold handshakes (TLS fused in), 0-RTT with the standard replay
  caveats, and connection IDs that survive IP changes.
- Every H3 deployment keeps H2 as fallback (Alt-Svc/HTTPS records);
  blocked UDP/443 degrades silently, not fatally.
- Gains concentrate on lossy/mobile paths; costs concentrate in server
  CPU — measure both before and after flipping it on.
