QUIC#
QUIC (RFC 9000) is the transport protocol underneath HTTP/3. Unlike everything else on this site, QUIC runs on UDP, the same UDP that’s been quietly carrying DNS and game traffic for decades, but rebuilds reliability, ordering, encryption, and multiplexing at the application layer. Originated at Google, run by Google / Cloudflare / Meta / Akamai, and standardized in 2021; QUIC carries a large share of public web traffic. Operators encounter it in three places: load balancers (HTTP/3 front ends), client traffic (browser → CDN), and capture analysis (it looks like UDP/443 noise unless you know what to do).
What QUIC is, in one paragraph: TCP + TLS 1.3 + multiplexing, moved into userspace and run over UDP. The handshake is one round trip (or zero with 0-RTT resumption). Streams are independent; loss on stream A doesn’t block stream B (no head-of-line blocking, HTTP/2’s chronic problem on TCP). Connections survive an IP / port change (mobile networks): the connection ID stays the same. The whole transport is encrypted, including most of the header.
Packet headers#
QUIC carries two header formats. The long header is used during the handshake and version negotiation; it carries explicit version, DCID, and SCID fields. The short header is used once the handshake completes; most of it is encrypted, and only the destination connection ID is visible to the wire.
packet-beta
title QUIC Long Header (handshake)
0: "H=1"
1: "F=1"
2-3: "Type"
4-7: "Type-specific"
8-39: "Version"
40-47: "DCID Len"
48-207: "Destination Connection ID (variable)"
208-215: "SCID Len"
216-375: "Source Connection ID (variable)"
packet-beta
title QUIC Short Header (1-RTT)
0: "H=0"
1: "F=1"
2: "S"
3: "R"
4: "R"
5: "K"
6-7: "PN Len"
8-167: "Destination Connection ID (variable)"
168-199: "Packet Number (variable)"
200-263: "Protected Payload"
The handshake#
QUIC + HTTP/3 versus the older TCP + TLS 1.2 + HTTP/2 stack:
Property |
QUIC + HTTP/3 TCP + TLS 1.2 + HTTP/2 |
|---|---|
Transport |
UDP/443 TCP/443 |
Encryption |
TLS 1.3 baked in TLS 1.2 / 1.3 layered on |
Handshake RTTs |
1 (or 0-RTT resumed) 2 (TCP) + 1-2 (TLS) |
Multiplexing |
Yes, no head-of-line block Yes, but blocked by TCP loss |
Connection migration |
Yes (connection ID) No (5-tuple bound) |
Header encryption |
Most of it None |
Userspace stack |
Yes Kernel TCP + userspace TLS |
sequenceDiagram
participant C as Client
participant S as Server (UDP/443)
C->>S: Initial (ClientHello, key share, ALPN h3)
S->>C: Initial (ServerHello, cert, key share, finished)
Note over C,S: 1-RTT handshake complete
C->>S: Handshake (finished) + 0-RTT app data (if resuming)
C->>S: Application: HTTP/3 frames over QUIC streams
S->>C: Application: HTTP/3 response
Servers + clients#
Tool |
Use |
|---|---|
|
Native HTTP/3 listener
( |
|
HTTP/3 by default since v2.6. |
|
QUIC front-end via OpenSSL-QUIC or quictls. |
|
HTTP/3 listener ( |
|
Cloudflare reference server. |
|
Microsoft’s QUIC stack (also in Linux). |
|
Operator’s first probe (needs a curl built with nghttp3 / ngtcp2 or quiche). |
|
Library + reference clients. |
|
TLS forks with QUIC API support. |
$ curl -I --http3 https://cloudflare.com
$ curl -v --http3 https://cloudflare.com 2>&1 | grep -E 'ALPN|HTTP'
# quiche reference client
$ quiche-client https://cloudflare-quic.com/
# check whether your curl has http3 enabled
$ curl --version | grep -o 'HTTP3'
Capture + analyze#
QUIC traffic is just UDP/443 on the wire. Without keys, there’s nothing to read past the first few bytes.
# capture to a pcap
$ sudo tcpdump -i any -nn -w quic.pcap 'udp port 443'
# decrypt in Wireshark: set SSLKEYLOGFILE before launching the client
$ export SSLKEYLOGFILE=/tmp/keys.log
$ curl --http3 https://example.com
# then in Wireshark: Edit -> Preferences -> Protocols -> TLS ->
# (Pre)-Master-Secret log filename: /tmp/keys.log
# nginx access log shows http2 vs http3
$ awk '{print $NF}' /var/log/nginx/access.log | sort -u | head
Firewall and ops gotchas#
UDP/443 must be open both ways. Many corporate egress proxies block UDP except DNS/NTP; QUIC silently fails over to TCP/TLS.
Alt-Svcheader is how the server advertises HTTP/3 availability; clients fall back if QUIC fails.MTU and fragmentation. QUIC uses Path MTU Discovery and refuses to be fragmented. Black-holes show up as “first request works, larger ones hang.”
Stateless retry / connection ID breaks naive load balancers so you can’t 5-tuple hash. Use connection-ID-aware LBs (HAProxy
proto quic, Envoy QUIC, Cloudflare).Capture without keys is opaque. Plan for
SSLKEYLOGFILEon endpoints you control, or terminate QUIC at a load balancer you can introspect.HTTP/3 isn’t QUIC. HTTP/3 is one application of QUIC; SMB, DNS-over-QUIC (RFC 9250), and proprietary protocols also use it.
See also#
UDP, the transport QUIC rides on.
HTTP, the HTTP/1.1 / HTTP/2 / HTTP/3 progression.
SSL/TLS, TLS 1.3, which QUIC bakes in.
RFC 9000 (QUIC), RFC 9001 (TLS+QUIC), RFC 9002 (loss / congestion), RFC 9114 (HTTP/3).
cloudflare-quic.com, public test endpoint.