Protocol Deep Dives

RFC 9114: HTTP/3 Protocol Deep Dive

How HTTP/3 replaces TCP with QUIC — connection migration, 0-RTT handshakes, stream multiplexing without head-of-line blocking, QPACK header compression, and deployment considerations with Alt-Svc.

Why HTTP/3? The Problem With TCP

HTTP/2 solved a major problem: by multiplexing many requests over a single TCP connection it eliminated the HTTP/1.1 requirement of opening 6 parallel TCP connections per host. But multiplexing inside TCP exposed a deeper flaw — TCP head-of-line blocking.

TCP delivers bytes in order. When a single packet is lost, the kernel's receive buffer freezes every stream waiting for the retransmission, even streams whose data arrived safely. On a 1% packet-loss mobile network, HTTP/2 with 100 multiplexed requests performs *worse* than HTTP/1.1 with 6 separate connections because a single loss stalls everything.

HTTP/3 solves this by replacing TCP with QUIC (RFC 9000) — a transport protocol built on UDP that implements its own reliable delivery per stream.

HTTP/1.1:  TCP connection per request (6 parallel max)
HTTP/2:    Multiple streams, one TCP connection → HOL blocking
HTTP/3:    Multiple streams, one QUIC connection → independent loss recovery

TLS 1.3 Is Built In

HTTP/3 mandates TLS 1.3 — there is no cleartext HTTP/3. QUIC integrates the TLS 1.3 handshake into its own connection setup, so the TLS negotiation happens in parallel with QUIC transport handshake parameters rather than sequentially after TCP. This shaves a full round-trip from connection establishment.

HTTP/1.1 + TLS 1.2:  TCP SYN → SYN-ACK → ACK →  TLS ClientHello → ... (3-RTT)
HTTP/2  + TLS 1.3:   TCP SYN → SYN-ACK → ACK →  TLS 1.3 1-RTT  (2-RTT)
HTTP/3  + QUIC:      QUIC Initial → Handshake → 1-RTT data  (1-RTT, 0-RTT on resume)

QUIC Transport Layer

Connection IDs, Not 4-Tuples

TCP connections are identified by the 4-tuple: (src-IP, src-port, dst-IP, dst-port). When a mobile device switches from Wi-Fi to cellular, all four values change and every TCP connection is destroyed.

QUIC uses opaque Connection IDs — random bytes chosen by the endpoints that identify the logical connection regardless of which network path carries the packets. When the client moves to a new IP address, it validates the new path and continues the QUIC connection without interruption. This is connection migration, and it means a download or video call survives a Wi-Fi handoff.

QUIC Packet Header (simplified):
┌──────────────────────────────────────────────────┐
│  Header Form (1) │ Version (32 bits, Long only)  │
│  Destination Connection ID (0–20 bytes)          │
│  Source Connection ID (0–20 bytes, Long only)    │
│  Payload (encrypted)                              │
└──────────────────────────────────────────────────┘

Stream Multiplexing Without HOL Blocking

QUIC streams are independent byte-streams numbered by stream ID. Loss of a packet carrying stream 5's data stalls *only* stream 5's reassembly — streams 3, 7, and 12 continue delivering data to the application without waiting.

Stream IDs encode direction and initiator:

ID mod 4DirectionInitiator
0BidirectionalClient
1BidirectionalServer
2UnidirectionalClient
3UnidirectionalServer

HTTP/3 uses client-initiated bidirectional streams (IDs 0, 4, 8, ...) for request/response pairs. Control streams and QPACK streams use unidirectional streams.

0-RTT Resumption

On a repeated connection to a known server, QUIC can send application data in the very first packet using a Pre-Shared Key (PSK) from a previous session. This is 0-RTT resumption: the client sends the ClientHello together with encrypted HTTP/3 request data before receiving any server response.

The trade-off: 0-RTT data is replay-vulnerable. An attacker who captures the 0-RTT packet could resend it. HTTP/3 limits 0-RTT to idempotent requests (GET, HEAD) and recommends servers add anti-replay mechanisms for sensitive endpoints.

HTTP/3 Framing — QPACK Header Compression

Frame Types

HTTP/3 defines its own frame format carried inside QUIC streams, distinct from HTTP/2's binary frames (which rode inside TCP).

HTTP/3 Frame:
┌───────────────────────────┐
│  Type (variable-length)   │
│  Length (variable-length) │
│  Payload                  │
└───────────────────────────┘

Core frame types (RFC 9114):
  0x00  DATA         — request/response body bytes
  0x01  HEADERS      — QPACK-encoded header block
  0x07  GOAWAY       — graceful shutdown, last stream ID
  0x04  SETTINGS     — connection-level parameters
  0x05  PUSH_PROMISE — server push (HTTP/3 still supports push)

QPACK vs HPACK

HTTP/2 used HPACK (RFC 7541) for header compression, but HPACK requires ordered delivery — both endpoints maintain a synchronized compression context, and an out-of-order header block would corrupt it. That ordering requirement conflicted with QUIC's independent-stream design.

QPACK (RFC 9204) solves this with two dedicated unidirectional control streams:

  • Encoder stream (client → server): sends dynamic table updates
  • Decoder stream (server → client): sends acknowledgements of table entries

Request headers can either reference the static table (61 predefined entries) or use a Required Insert Count to signal which dynamic table state they depend on. If the required entries have not yet arrived via the encoder stream, the decoder blocks only that specific QUIC stream — not the entire connection.

QPACK static table examples (index → header):
  1  → :authority
  2  → :method: GET
  3  → :method: POST
 24  → :status: 404
 25  → :status: 503
 95  → content-type: text/html; charset=utf-8

Deployment — Alt-Svc and Graceful Fallback

Advertising HTTP/3 With Alt-Svc

Since browsers do not know in advance whether a server supports HTTP/3, the first connection typically uses HTTP/2 over TCP. The server advertises HTTP/3 support via the Alt-Svc response header:

Alt-Svc: h3=":443"; ma=86400

This tells the browser: an HTTP/3 endpoint is available on the same host at port 443, and this advertisement is valid for 86400 seconds (1 day). On the next request (or concurrently via a QUIC race), the browser tries UDP port 443. If the QUIC handshake completes before the TCP fallback, subsequent requests use HTTP/3.

Cloudflare, Nginx (1.25+), and Caddy all support this flow out of the box.

Nginx HTTP/3 Configuration

server {
    listen 443 ssl;
    listen 443 quic reuseport;     # HTTP/3 on UDP 443
    http2 on;
    http3 on;

    ssl_certificate     /etc/ssl/cert.pem;
    ssl_certificate_key /etc/ssl/key.pem;
    ssl_protocols       TLSv1.3;   # TLS 1.3 required

    add_header Alt-Svc 'h3=":443"; ma=86400';
}

Load Balancer QUIC Support

QUIC presents operational challenges for load balancers and middleboxes:

  • Stateful routing: QUIC connections must reach the *same* backend server because the QUIC state (encryption keys, flow control) is per-server. Use Connection ID-based routing rather than IP-based.
  • UDP firewall rules: Many enterprise firewalls block outbound UDP on port 443. QUIC automatically falls back to TCP+TLS — ensure your server also serves HTTP/2 on TCP 443.
  • QUIC-LB (RFC 9484): A standard for embedding routing tokens inside Connection IDs so load balancers can route without decrypting the packet.

Performance Analysis

Lossy Network Improvements

HTTP/3's advantage increases dramatically as packet loss increases:

Packet LossHTTP/2 vs HTTP/1.1HTTP/3 vs HTTP/2
0%+30% faster~same
1%~same (HOL cancels gains)+20% faster
2%-20% slower+40% faster
5%-40% slower+80% faster

These numbers (from Google's original QUIC deployment data) show that HTTP/3 is most valuable on mobile networks, satellite links, and congested Wi-Fi — exactly the conditions that dominate global web traffic.

Connection Establishment Latency

On a fresh connection:

HTTP/1.1 + TLS 1.2: 3 RTTs before first byte
HTTP/2  + TLS 1.3: 2 RTTs before first byte
HTTP/3  + QUIC:    1 RTT before first byte
HTTP/3  + 0-RTT:   0 RTT (data in first packet, replay caveat)

On a 50ms RTT connection (typical US domestic), HTTP/3 saves 50ms vs HTTP/2 and 100ms vs HTTP/1.1 on the connection setup alone — before any protocol-level optimizations like multiplexing.

Real-World Deployment Status (2024)

  • Cloudflare: All 300+ edge PoPs support HTTP/3 by default
  • Google: All Google properties and CDN support HTTP/3
  • Nginx: HTTP/3 in mainline since 1.25.0 (2023)
  • Browser support: Chrome 87+, Firefox 88+, Safari 14+, Edge 87+
  • ~30% of the web served over HTTP/3 as of late 2024 (Cloudflare Radar data)

HTTP/3 is not a future technology — it is the current default for most CDN-fronted applications. If your stack uses Cloudflare, Fastly, or AWS CloudFront, your users are likely already getting HTTP/3 without any application code changes.

Related Protocols

Related Glossary Terms

More in Protocol Deep Dives