Skip to content

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.

TCP plus TLS handshake versus QUIC handshake, showing QUIC's fewer round trips
Source: Wikimedia Commons. CC BY-SA.
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 --http3
  • quiche-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.

Further reading#

Video walkthrough

HTTP 1 vs HTTP 2 vs HTTP 3 : via ByteByteGo