HTTP/3 and QUIC#
HTTP/3 is HTTP/2's framing over a new transport called QUIC, which runs on UDP and bundles TLS 1.3, congestion control, and multiplexing into one protocol. The biggest win: a lost packet stalls only the affected stream, not all of them.
flowchart TB
HTTP3[HTTP/3 semantics] --> QUIC
QUIC[QUIC<br/>streams + congestion + TLS 1.3] --> UDP
UDP --> IP
classDef service fill:#fef3c7,stroke:#92400e,stroke-width:1px,color:#0f172a;
class HTTP3,QUIC service;
classDef client fill:#dbeafe,stroke:#1e40af,stroke-width:1px,color:#0f172a;
classDef edge fill:#cffafe,stroke:#0e7490,stroke-width:1px,color:#0f172a;
classDef service fill:#fef3c7,stroke:#92400e,stroke-width:1px,color:#0f172a;
classDef datastore fill:#fee2e2,stroke:#991b1b,stroke-width:1px,color:#0f172a;
classDef cache fill:#fed7aa,stroke:#9a3412,stroke-width:1px,color:#0f172a;
classDef queue fill:#ede9fe,stroke:#5b21b6,stroke-width:1px,color:#0f172a;
classDef compute fill:#d1fae5,stroke:#065f46,stroke-width:1px,color:#0f172a;
classDef storage fill:#e5e7eb,stroke:#374151,stroke-width:1px,color:#0f172a;
classDef external fill:#fce7f3,stroke:#9d174d,stroke-width:1px,color:#0f172a;
classDef obs fill:#f3e8ff,stroke:#6b21a8,stroke-width:1px,color:#0f172a;
Key gains over HTTP/2 on TCP: no TCP-level head-of-line blocking, 0-RTT resumption for repeat connections, connection migration when your IP changes, and faster handshake (1-RTT for new, 0-RTT for resumed).
QUIC originated at Google in 2012; the IETF standardized it as RFC 9000 in 2021. HTTP/3 (RFC 9114) maps HTTP/2's semantics onto QUIC.
Why a new transport?#
flowchart TB
subgraph H2[HTTP/2 on TCP]
S1H2[Stream 1] --> TCP
S2H2[Stream 2] --> TCP
TCP -. one segment lost .-> Block[blocks all streams]
end
subgraph H3[HTTP/3 on QUIC]
S1H3[Stream 1] --> QUIC
S2H3[Stream 2] --> QUIC
QUIC -. packet for stream 1 lost .-> OK[stream 2 keeps flowing]
end
classDef client fill:#dbeafe,stroke:#1e40af,stroke-width:1px,color:#0f172a;
classDef edge fill:#cffafe,stroke:#0e7490,stroke-width:1px,color:#0f172a;
classDef service fill:#fef3c7,stroke:#92400e,stroke-width:1px,color:#0f172a;
classDef datastore fill:#fee2e2,stroke:#991b1b,stroke-width:1px,color:#0f172a;
classDef cache fill:#fed7aa,stroke:#9a3412,stroke-width:1px,color:#0f172a;
classDef queue fill:#ede9fe,stroke:#5b21b6,stroke-width:1px,color:#0f172a;
classDef compute fill:#d1fae5,stroke:#065f46,stroke-width:1px,color:#0f172a;
classDef storage fill:#e5e7eb,stroke:#374151,stroke-width:1px,color:#0f172a;
classDef external fill:#fce7f3,stroke:#9d174d,stroke-width:1px,color:#0f172a;
classDef obs fill:#f3e8ff,stroke:#6b21a8,stroke-width:1px,color:#0f172a;
TCP guarantees in-order delivery of bytes - so a missing segment stalls every stream sharing that connection. QUIC delivers per-stream ordering only; loss on one stream doesn't block others.
Handshake#
sequenceDiagram
participant C as Client
participant S as Server
Note over C,S: New connection (1-RTT)
C->>S: Initial(ClientHello + crypto)
S->>C: Initial(ServerHello + crypto) + Handshake(cert, finished)
C->>S: Handshake(finished) + 1-RTT(application data)
Note over C,S: 0-RTT resumed
C->>S: Initial(ClientHello+early_data) + 0-RTT(data)
S->>C: response
- TLS 1.3 is mandatory and integrated; no separate TLS handshake.
- 1-RTT for fresh connections (vs TCP+TLS 1.3 = 2 RTT).
- 0-RTT for resumption: client sends app data on the first flight. Risk: replay attacks on non-idempotent endpoints; clients SHOULD use 0-RTT only for safe methods.
Streams#
QUIC has bidirectional and unidirectional streams. Each is an independent byte sequence:
| Stream id | Initiated by | Direction |
|---|---|---|
4n |
Client | Bidirectional |
4n+1 |
Server | Bidirectional |
4n+2 |
Client | Unidirectional |
4n+3 |
Server | Unidirectional |
HTTP/3 maps each HTTP request/response to a single bidirectional stream.
Connection IDs and migration#
flowchart LR
C1([Client wifi]) -->|CID=abc123| S[Server]
C1 -.switch to cell.-> C2([Client cell])
C2 -->|same CID=abc123| S
classDef client fill:#dbeafe,stroke:#1e40af,stroke-width:1px,color:#0f172a;
classDef edge fill:#cffafe,stroke:#0e7490,stroke-width:1px,color:#0f172a;
classDef service fill:#fef3c7,stroke:#92400e,stroke-width:1px,color:#0f172a;
classDef datastore fill:#fee2e2,stroke:#991b1b,stroke-width:1px,color:#0f172a;
classDef cache fill:#fed7aa,stroke:#9a3412,stroke-width:1px,color:#0f172a;
classDef queue fill:#ede9fe,stroke:#5b21b6,stroke-width:1px,color:#0f172a;
classDef compute fill:#d1fae5,stroke:#065f46,stroke-width:1px,color:#0f172a;
classDef storage fill:#e5e7eb,stroke:#374151,stroke-width:1px,color:#0f172a;
classDef external fill:#fce7f3,stroke:#9d174d,stroke-width:1px,color:#0f172a;
classDef obs fill:#f3e8ff,stroke:#6b21a8,stroke-width:1px,color:#0f172a;
QUIC connections are identified by a Connection ID, not the IP-port 4-tuple. When a phone switches from wifi to cellular, the IP changes but the connection survives. TCP would force a full handshake from scratch.
Loss detection and congestion control#
- ACK frames carry SACK-like ranges natively.
- Packet number is monotonic and never reused, even for retransmits. Disambiguates true loss vs reorder.
- Congestion control is pluggable; default is NewReno-like, but CUBIC and BBR are widely deployed.
QPACK - HPACK with reordering#
HPACK in HTTP/2 needs strict header-frame order; QUIC's reordering breaks that. QPACK adds two unidirectional streams (encoder and decoder) to keep dynamic tables in sync without serial ordering.
Deployment caveats#
- UDP firewall hostility: many enterprise firewalls block UDP except for DNS. Clients fall back to HTTP/2 over TCP.
- Connection ID rewriting: load balancers must route on Connection ID, not 4-tuple. Most LBs now support this.
- CPU cost: QUIC packet handling is more expensive than TCP per packet because TLS is in user space. Kernel-bypass (eBPF, AF_XDP) helps.
- NAT rebinding: short UDP NAT bindings mean clients must retry quickly if a mapping expires.
Where HTTP/3 is deployed#
| Provider | Status |
|---|---|
| Cloudflare | Full HTTP/3 |
| Google (YouTube, Search) | Full |
| Meta (Facebook, Instagram) | Full |
| Fastly | Full |
| AWS CloudFront | Full |
| Nginx | Stable |
| Caddy, h2o | Native |
Mobile carriers like T-Mobile and Verizon have improved QUIC parity with TCP over time. Some still throttle UDP.
How HTTP/3 fits in the stack#
flowchart TB
H3((HTTP/3 +<br/>QUIC))
UDP[UDP use cases<br/>transport]
TLS[TLS / mTLS<br/>TLS 1.3 baked in]
H2[HTTP/2 deep dive<br/>predecessor]
HTTP[HTTP protocols<br/>parent comparison]
UDP --> H3
TLS --> H3
H2 -. evolves to .-> H3
HTTP --> H3
classDef client fill:#dbeafe,stroke:#1e40af,stroke-width:1px,color:#0f172a;
classDef edge fill:#cffafe,stroke:#0e7490,stroke-width:1px,color:#0f172a;
classDef service fill:#fef3c7,stroke:#92400e,stroke-width:1px,color:#0f172a;
classDef datastore fill:#fee2e2,stroke:#991b1b,stroke-width:1px,color:#0f172a;
classDef cache fill:#fed7aa,stroke:#9a3412,stroke-width:1px,color:#0f172a;
classDef queue fill:#ede9fe,stroke:#5b21b6,stroke-width:1px,color:#0f172a;
classDef compute fill:#d1fae5,stroke:#065f46,stroke-width:1px,color:#0f172a;
classDef storage fill:#e5e7eb,stroke:#374151,stroke-width:1px,color:#0f172a;
classDef external fill:#fce7f3,stroke:#9d174d,stroke-width:1px,color:#0f172a;
classDef obs fill:#f3e8ff,stroke:#6b21a8,stroke-width:1px,color:#0f172a;
class H3 service;
class UDP,TLS,H2,HTTP datastore;
Glossary & fundamentals#
| Tag | Concept | What it is | Page |
|---|---|---|---|
HLD |
HTTP protocols | the comparison page | http-protocols |
HLD |
UDP use cases | the underlying transport | udp-use-cases |
HLD |
HTTP2 deep dive | the previous version | http2-deep-dive |
HLD |
TLS mTLS | TLS 1.3 baked into QUIC | tls-mtls |
Quick reference#
Key wins over HTTP/2#
- No TCP-level HOL (per-stream loss recovery)
- 1-RTT handshake (vs 2-RTT for TCP+TLS)
- 0-RTT resumption (with replay caveats)
- Connection migration across IP changes
- TLS 1.3 mandatory and integrated
Stream ids#
| Pattern | Initiator | Direction |
|---|---|---|
| 4n | Client | Bidirectional |
| 4n+1 | Server | Bidirectional |
| 4n+2 | Client | Unidirectional |
| 4n+3 | Server | Unidirectional |
QPACK#
HPACK adapted for reordering; encoder/decoder streams keep dynamic tables consistent.
Deployment caveats#
- UDP often blocked at corporate firewalls → fall back to HTTP/2
- LB must route on Connection ID, not 4-tuple
- CPU cost higher; kernel-bypass helps
- Short UDP NAT bindings → quick retry
0-RTT replay#
Servers must restrict 0-RTT to idempotent/safe methods (GET).
Tools#
- Chrome
chrome://net-internals/#quic curl --http3quiche-client(Cloudflare)aioquic(Python)
Refs#
- RFC 9000 - QUIC
- RFC 9114 - HTTP/3
- Cloudflare HTTP/3 from A to Z
- Marx - HOL blocking series
FAQ#
Why does HTTP/3 use UDP instead of TCP?#
UDP lets QUIC implement its own streams, congestion control, and TLS 1.3 in user space, avoiding TCP's kernel-level head-of-line blocking and slow rollout cycle.
What is 0-RTT in QUIC?#
0-RTT lets a resumed QUIC connection send application data on the very first packet using a cached session ticket, eliminating handshake round-trip latency.
What is QUIC connection migration?#
QUIC identifies a connection by a connection ID instead of the IP and port tuple, so a phone moving from Wi-Fi to cellular can keep the same session alive.
Is HTTP/3 faster than HTTP/2?#
On lossy or mobile networks, yes. HTTP/3 avoids TCP head-of-line blocking, so a single dropped packet stalls only one stream, not the entire connection.
When should I use HTTP/3?#
Use HTTP/3 for mobile, global, or high-latency clients where 0-RTT and per-stream loss isolation matter. Most CDNs negotiate HTTP/3 automatically when supported.
Related Topics#
- HTTP/2 Deep Dive: the version HTTP/3 evolved from
- UDP Use Cases: why QUIC chose UDP
- TLS and mTLS: TLS 1.3 is integrated into QUIC
Further reading#
- RFC - RFC 9000 - QUIC: A UDP-Based Multiplexed and Secure Transport
- RFC - RFC 9114 - HTTP/3
- Blog - Cloudflare - HTTP/3 from A to Z
- Blog - Robin Marx - HTTP/3 explainer series