← Networking Mastery — Fundamentals to Principal

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

YearEvent
2012Jim Roskind at Google starts experimenting with QUIC
2013Google deploys QUIC experiment to Chrome + Google servers
2015Google publishes QUIC spec, IETF starts standardization
2018IETF draft named “HTTP/3” (previously HTTP-over-QUIC)
2021RFC 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
ScenarioTCP+TLS 1.2TCP+TLS 1.3QUIC newQUIC resumed
RTTs before data3210
Absolute latency @ 50ms RTT150ms100ms50ms~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:

  1. Request streams — carry actual HTTP requests/responses (many, bidirectional)
  2. Encoder stream — client → server, carries dynamic table updates (one, unidirectional)
  3. 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

ServerHTTP/3 Support
CaddyBuilt-in, enabled by default
nginxVia patch (quiche) or nginx-quic branch
LiteSpeedNative support
CloudflareAll traffic via QUIC
AWS CloudFrontGA since 2022
FastlySupported
ApacheNot 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?

ScenarioImpactWhy
Mobile users (LTE, 5G)HighLossy links, connection migration
International / high latencyHighFewer RTTs for connection setup
WiFi in crowded spacesHighPacket loss common
Video streamingMedium-HighMigration, lower rebuffering
Data center to data centerLowReliable links, latency already low
Single large file downloadLowNo multiplexing benefit, one stream
API calls (small payloads)Medium0-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

FeatureHTTP/1.1HTTP/2HTTP/3
TransportTCPTCPQUIC (UDP)
MultiplexingNo (6 connections)Yes (one TCP conn)Yes (one QUIC conn)
HOL blockingYes (per-conn)Yes (TCP-level)No (stream-independent)
Header compressionNoneHPACKQPACK
Connection setupTCP+TLS: 2-3 RTTsTCP+TLS: 2 RTTsQUIC: 1 RTT / 0-RTT
EncryptionOptional (HTTPS)Optional (HTTPS)Mandatory (TLS 1.3)
Connection migrationNoNoYes (Connection IDs)
PrioritizationHacky (workarounds)Binary framing + priorityQUIC priorities
Server pushNoYes (rarely used)Yes (deprecated in practice)
Browser supportUniversalUniversalAll major browsers
UDP blocking riskN/AN/A5-15% enterprise
Best forSimple/legacyMost web trafficLossy/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.