HTTP/3 and QUIC
HTTP/3 is HTTP's semantics — same methods, headers, status codes — running over a brand-new transport called QUIC. QUIC is built on UDP, folds TLS 1.3 directly into the transport handshake, gives every stream its own loss recovery, and can survive an IP address change without dropping the connection. It is the largest shift in internet transport since TCP, and it ships invisibly: every major browser and most large sites already negotiate it.
The reason it exists is the wall HTTP/2 hit. HTTP/2 multiplexed streams over a single TCP connection, but TCP's in-order delivery meant one lost packet stalled every stream. QUIC moves stream management out of the kernel's TCP into user space over UDP, so a lost packet stalls only the stream whose data it carried, not the whole connection. It also collapses the transport and TLS handshakes into one, cutting connection setup to 0–1 round trips. Two old costs — head-of-line blocking and handshake latency — fall together.
Why QUIC Lives in User Space over UDP
TCP is implemented in the operating system kernel, which makes it nearly impossible to evolve — a change to TCP has to ship in billions of kernels and survive every middlebox that inspects TCP options. QUIC sidesteps this by running in user space, as a library inside the application, over plain UDP datagrams that the kernel just forwards. A browser or server can ship a new QUIC version in an ordinary software update, no kernel involved.
UDP is the carrier precisely because it is the lowest-level transport the internet reliably passes — it gives QUIC raw datagrams and gets out of the way, leaving QUIC to build reliability, ordering, and congestion control itself. The cost is real: QUIC's crypto and packet handling run in user space, so at very high request rates it burns more CPU than kernel TCP, which has decades of offload and tuning. That CPU cost is the main reason large fleets watch QUIC's per-core throughput carefully.
Integrated TLS 1.3 and 0/1-RTT Setup
In the TCP world, a connection costs a TCP handshake (one round trip) and then a TLS handshake (one or two more) before any HTTP byte moves. QUIC merges them: the transport and TLS 1.3 handshakes are one exchange. A first-time client reaches an encrypted, ready connection in a single round trip — 1-RTT — instead of the two or three TCP+TLS needed.
For a client that has connected before, QUIC offers 0-RTT: it sends application data in the very first packet, using keys cached from the prior session, so the request arrives with zero handshake delay. The catch is replay — 0-RTT data can be captured and re-sent by an attacker, because the server has not yet proven freshness, so 0-RTT must be restricted to idempotent requests like GET. Sending a payment in a 0-RTT packet invites a replayed double-charge.
Independent Streams and Per-Stream Loss Recovery
QUIC's defining feature is that streams are independent at the transport level. Each stream has its own sequence space and its own delivery, so a lost packet only holds back the stream whose data it carried — every other stream keeps delivering. This is the exact thing TCP could not do: TCP saw HTTP/2's streams as one byte stream and stalled them all on any loss.
The payoff shows on lossy networks. On a mobile link with 2% packet loss, HTTP/2 over TCP stalls every concurrent download on each lost segment; HTTP/3 over QUIC loses progress only on the one stream that lost a packet. The head-of-line blocking that capped HTTP/2 is gone — not moved, removed — because the multiplexing and the loss recovery finally live at the same layer.
# curl negotiates HTTP/3 over QUIC (UDP/443) with --http3 curl --http3 -v https://example.com/ * Connected to example.com (UDP) <- UDP, not TCP * using HTTP/3 * h3 [:method: GET] * h3 [:path: /] < HTTP/3 200 <- same semantics, new transport < alt-svc: h3=":443"; ma=86400 <- how clients discover h3
Connection Migration
A TCP connection is identified by the four-tuple of source IP, source port, destination IP, destination port. Change any of them — walk out of Wi-Fi range onto cellular, and your source IP changes — and the connection is dead; you reconnect and re-handshake from scratch. This is why a video call or download dies the moment your phone switches networks.
QUIC identifies a connection by a connection ID carried inside the packet, not by the IP four-tuple. When your IP changes, the connection ID stays the same, so the server recognizes the migrated packets as the same connection and continues without a new handshake — connection migration. The download keeps going across the network switch. For mobile clients that constantly roam between Wi-Fi and cellular, this is the feature that turns dropped connections into uninterrupted handoffs — and it is impossible in TCP by construction.
TCP + TLS + HTTP/2 stacks three handshakes (TCP, then TLS, then HTTP/2 settings), runs in the kernel where it is hard to evolve, shares one ordered byte stream so any loss stalls all streams, and dies when the client's IP changes. It is mature, CPU-cheap per connection, and the safe fallback everywhere UDP is blocked.
QUIC + HTTP/3 folds transport and TLS 1.3 into one 0/1-RTT handshake, runs in user space over UDP so it ships in app updates, recovers loss per stream so one drop stalls only its own stream, and migrates across an IP change via the connection ID. It costs more CPU and needs a TCP fallback path. Prefer it for mobile and lossy networks; keep TCP for UDP-hostile paths and CPU-bound fleets.
- Assuming UDP is reachable everywhere and shipping HTTP/3 with no fallback. Many corporate and hotel networks block or throttle UDP/443; without a TCP+HTTP/2 fallback, those clients simply cannot connect at all.
- Ignoring the CPU cost of user-space crypto at scale. QUIC's per-packet encryption runs in user space without the kernel's TCP offload, so a fleet that flips to HTTP/3 can see per-core throughput drop and capacity quietly shrink.
- Putting payment or other non-idempotent requests in a 0-RTT packet. 0-RTT data is replayable before the server proves freshness, so an attacker can re-send it — restrict 0-RTT to idempotent GETs or risk a replayed double action.
- Expecting old middleboxes to pass QUIC cleanly. Boxes that deep-inspect or rate-limit UDP can mangle or drop QUIC packets, causing intermittent failures that look like random connection resets until you correlate them with the network path.
- Believing HTTP/3 still has head-of-line blocking like HTTP/2. It does not at the transport — streams are independent — but assuming it does (or assuming it cannot help on a lossy link) leaves the migration and per-stream recovery wins on the table.
- Advertise HTTP/3 with
Alt-Svcbut always keep a TCP+HTTP/2 listener, so clients on UDP-blocked networks fall back cleanly instead of failing to connect. - Restrict 0-RTT to idempotent methods and reject replayable side-effecting requests in early data, so a captured 0-RTT packet cannot trigger a duplicate action.
- Benchmark QUIC's per-core CPU under your real request rate before a fleet-wide cutover, and size capacity for the user-space crypto overhead rather than assuming TCP's numbers carry over.
- Prefer HTTP/3 for mobile and high-loss clients where connection migration and per-stream recovery pay off, and keep HTTP/2 for wired, low-loss, CPU-bound paths.
- Monitor QUIC handshake and UDP failure rates separately from TCP, so middlebox interference shows up as a distinct signal instead of hiding inside aggregate connection errors.
Knowledge Check
How does QUIC eliminate the head-of-line blocking that HTTP/2 over TCP could not?
- Each stream has independent loss recovery, so a lost packet stalls only its own stream
- It retransmits lost packets faster than TCP, so stalls clear before they matter
- It opens a completely separate dedicated UDP connection for every single stream specifically to isolate any loss
- UDP delivers packets with no ordering, so blocking is impossible by design
A phone moves from Wi-Fi to cellular mid-download. Why does QUIC survive the switch when TCP would not?
- QUIC identifies the connection by a connection ID, not the IP four-tuple that just changed
- QUIC performs a fast new handshake on the cellular IP before the old one drops
- QUIC keeps fully parallel redundant connections open on both networks at once so that one of them always survives
- UDP is stateless, so the IP change is irrelevant to it
Why must 0-RTT data be limited to idempotent requests?
- It can be captured and replayed before the server proves freshness, so a non-idempotent action could repeat
- It is sent unencrypted in the first packet, so secrets would leak in plaintext
- The first packet is too small to hold a non-idempotent request body
- Enabling 0-RTT early data permanently disables QUIC connection migration, which all non-idempotent side-effecting calls happen to depend on
You got correct