L4 · transport · reviewed
QUIC
QUIC
UDP-carried, always encrypted multiplexed transport: streams do not share TCP’s head-of-line block, and connection IDs survive path changes.
Presenter modeEmbed this figure
Why it exists
TCP+TLS costs an extra round trip before application data and couples every multiplexed stream to one ordered byte stream. QUIC combines transport and cryptographic handshake and gives each stream independent delivery over UDP. OverviewRFC 9000 · PROPOSED STANDARD · May 2021
Initial long header starts the handshake
The client sends a QUIC Initial inside UDP — already carrying cryptographic handshake data. After 1-RTT the application can send. There is no TCP three-way handshake to watch for.
The client sends a long-header Initial packet (Header Form 1, type Initial) in a UDP datagram. Version is 1. Destination Connection ID is chosen so the server can route the handshake; TLS ClientHello is already inside the protected payload. Client: Header long · type Initial. UDP path. Server · :443.
- Link
- Blocking
- Packet in flight
- Discarded
- Emphasis
Text equivalent of this diagram
| Element | Kind | State |
|---|---|---|
| Client | host | Header: long · type Initial |
| UDP path | cloud | — |
| Server · :443 | host | — |
| Client — UDP path | link | up |
| UDP path — Server · :443 | link | up |
The client sends a long-header Initial packet (Header Form 1, type Initial) in a UDP datagram. Version is 1. Destination Connection ID is chosen so the server can route the handshake; TLS ClientHello is already inside the protected payload.
What changed
- Emphasis on Client
- Initial: Client → UDP path
- Client: Header → long · type Initial
- TLS inside first flight
How it works
Long headers (Initial, Handshake, 0-RTT, Retry) carry version and connection IDs during setup. Short headers carry 1-RTT data afterward. Long Header PacketsRFC 9000 · PROPOSED STANDARD · May 2021
Streams multiplex application data. Loss recovery is per-packet; a lost packet stalls only streams that needed those frames. StreamsRFC 9000 · PROPOSED STANDARD · May 2021
Connection IDs identify the connection across address changes. Migration probes a new path without tearing down the session. Connection MigrationRFC 9000 · PROPOSED STANDARD · May 2021
HTTP/3 maps HTTP semantics onto QUIC streams. QUIC itself is a general transport; HTTP/3 is one application. IntroductionRFC 9114 · PROPOSED STANDARD · June 2022
On the wire
Constructed examples, encoded from the field table below them — not captured traffic.
- UDP
- QUIC is always carried in UDP. Destination port 443 is common for HTTP/3; the four-tuple is not the connection identity — connection IDs are. RFC 9000
- QUIC long header
- Header Form 1, Fixed Bit 1, Long Packet Type, Type-Specific Bits, Version, Destination and Source Connection ID lengths and values, then type-specific fields. RFC 9000
- Type-specific payload
- For Initial: Token Length (varint), Token, Length (varint), Packet Number, and protected payload. Those fields are not fixed-width on the wire. RFC 9000
Configure it
server { listen 443 ssl; listen 443 quic reuseport; http2 on; http3 on; ssl_certificate /etc/ssl/certs/example.pem; ssl_certificate_key /etc/ssl/private/example.key; add_header Alt-Svc 'h3=":443"; ma=86400' always; }QUIC requires a UDP listen alongside TCP TLS. Alt-Svc tells HTTP/1.1 and HTTP/2 clients that h3 is available on UDP/443.
Common mistake: Opening only TCP/443 and wondering why no client ever sends an Initial — discovery never points at UDP.
RFC 9114 §3.1
Verify
ss -ulnp | grep 443- UDP 443 bound by nginx.
curl --http3 -I https://example.com/- HTTP/3 response when the path permits UDP.
Caveats
- Marked draft: nginx QUIC builds and directives change across releases.
- Path firewalls must allow UDP/443 or clients fall back silently to HTTP/2.
When it breaks
Symptom first, because that is what you have when it happens.
A client falls back to HTTP/2 over TCP and never uses HTTP/3, with no error shown.
Narrow it down
- Check whether UDP 443 is reachable end to end.
- Look for an Alt-Svc header or an HTTPS DNS record advertising h3.
- Test from a network without a corporate middlebox.
Cause
Something is blocking or rate-limiting UDP. Clients treat QUIC as best-effort and fall back silently, which is good for users and makes the failure invisible to operators.
Fix
Permit UDP 443. The silence is by design, so this needs to be checked deliberately rather than waited for.
Discovering an HTTP/3 EndpointRFC 9114 · PROPOSED STANDARD · June 2022QUIC throughput is well below TCP on the same path, on an otherwise healthy network.
Narrow it down
- Check whether the receiver is doing UDP segmentation offload, or handling every datagram in software.
- Compare CPU usage during the transfer against a TCP transfer.
- Look at whether the path rate-limits UDP.
Cause
QUIC runs in userspace and its per-packet cost is paid by the CPU rather than the NIC. Without offload, a fast link can be limited by packet processing rather than by the network.
Fix
Enable generic segmentation offload for UDP where available, and measure CPU alongside throughput before concluding the network is at fault.
Connections break when a client changes network, despite QUIC advertising connection migration.
Narrow it down
- Check whether a middlebox on the path is keying state on the four-tuple.
- Confirm the server supports migration and has not disabled it.
- Look at whether the connection ID is being rewritten.
Cause
Migration works because a connection is identified by a connection ID rather than by addresses. Anything that rewrites or keys on the four-tuple — some load balancers — defeats it.
Fix
Route QUIC by connection ID at the load balancer. Hashing the four-tuple works right up until a client moves, which is the case migration exists for.
Connection MigrationRFC 9000 · PROPOSED STANDARD · May 2021Handshake fails immediately; Version Negotiation or drops on first Initial.
Narrow it down
- Confirm the client and server share QUICv1 (0x00000001).
- Check middleboxes that inspect or drop unknown UDP payloads.
- Capture whether the server’s Initial returns at all.
Cause
No shared version, or a middlebox treating long-header QUIC as suspicious UDP. Unlike TCP, there is no familiar handshake for legacy filters to whitelist by shape alone.
Fix
Allow UDP 443 end to end; ensure both ends negotiate version 1; avoid UDP payload inspection that assumes DNS-shaped datagrams.
Version Negotiation PacketRFC 9000 · PROPOSED STANDARD · May 2021Intermittent failures behind a NAT or firewall that rewrites connection IDs or UDP ports aggressively.
Narrow it down
- Compare connection IDs on the wire at client and server.
- Check whether the NAT times out UDP state shorter than the QUIC idle timeout.
- Look for symmetric NAT behaviour after migration probes.
Cause
QUIC assumes connection IDs and path validation behave as specified. Devices that mutate CIDs or drop UDP mappings early break sessions that TCP’s longer-lived state machinery used to paper over.
Fix
Disable CID mutation; raise UDP timeouts for 443; prefer endpoints that issue new CIDs deliberately rather than relying on NAT stability.
Connection IDRFC 9000 · PROPOSED STANDARD · May 2021
Design notes
Clients treat QUIC as best-effort and fall back to TCP silently when UDP fails. That is good for users and bad for operators who never look at Alt-Svc or UDP reachability. Discovering an HTTP/3 EndpointRFC 9114 · PROPOSED STANDARD · June 2022
Userspace QUIC pays per-packet CPU cost that NIC TCP offloads used to hide. Measure CPU before blaming the path for low throughput.
Load balancers must understand connection IDs if migration matters. Four-tuple ECMP is enough only until the client moves.
Misconceptions
- “QUIC is faster because UDP is faster than TCP.”
- UDP is not faster; it is emptier. QUIC reimplements everything TCP does — reliability, ordering, congestion control — on top of it, and gains from combining the transport and TLS handshakes into one round trip and from removing head-of-line blocking between streams. OverviewRFC 9000 · PROPOSED STANDARD · May 2021
- “HTTP/3 is QUIC.”
- HTTP/3 is HTTP semantics mapped onto QUIC. QUIC is a general transport and carries other things; the two are separate specifications for that reason. IntroductionRFC 9114 · PROPOSED STANDARD · June 2022
- “QUIC removes head-of-line blocking entirely.”
- It removes it between streams: a lost packet stalls only the stream it belonged to, not every stream sharing the connection as in HTTP/2 over TCP. Within one stream, ordering still applies and a loss still blocks it.
More walkthroughs
Loss stalls one stream, not the connectioncomparison
HTTP/2 over TCP multiplexes streams on one byte stream — one lost segment stalls every stream. QUIC gives each stream its own delivery; loss isolates the damage.
The browser opens several streams on one QUIC connection: HTML, CSS, API. They share congestion control but not a single ordered byte pipe. Browser: Streams 0, 4, 8 open. Lossy hop. Origin.
- Link
- Blocking
- Packet in flight
- Discarded
- Emphasis
Text equivalent of this diagram
| Element | Kind | State |
|---|---|---|
| Browser | host | Streams: 0, 4, 8 open |
| Lossy hop | cloud | — |
| Origin | host | — |
| Browser — Lossy hop | link | up |
| Lossy hop — Origin | link | up |
The browser opens several streams on one QUIC connection: HTML, CSS, API. They share congestion control but not a single ordered byte pipe.
What changed
- Browser: Streams → 0, 4, 8 open
- multi-stream: Browser → Lossy hop
Connection ID survives a path changebaseline
A client moves from Wi-Fi to cellular. The connection continues because it is identified by connection ID. Load balancers that hash only the four-tuple break that promise.
The connection is established on Wi-Fi. Packets carry connection ID C1. The server and a CID-aware load balancer pin state to C1. Phone · CID C1: Path Wi-Fi four-tuple. Wi-Fi path. Cellular path. LB: Route by CID C1. Server.
- Link
- Blocking
- Packet in flight
- Discarded
- Emphasis
Text equivalent of this diagram
| Element | Kind | State |
|---|---|---|
| Phone · CID C1 | host | Path: Wi-Fi four-tuple |
| Wi-Fi path | cloud | — |
| Cellular path | cloud | — |
| LB | router | Route: by CID C1 |
| Server | host | — |
| Phone · CID C1 — Wi-Fi path | link | up |
| Phone · CID C1 — Cellular path | link | up |
| Wi-Fi path — LB | link | up |
| Cellular path — LB | link | up |
| LB — Server | link | up |
The connection is established on Wi-Fi. Packets carry connection ID C1. The server and a CID-aware load balancer pin state to C1.
What changed
- CID C1: Phone · CID C1 → Wi-Fi path
- LB: Route → by CID C1
- Phone · CID C1: Path → Wi-Fi four-tuple
Terms
- Connection ID
- A QUIC identifier for a connection that is independent of the UDP four-tuple. It is what makes connection migration possible when addresses change.
- QUIC stream
- An independent ordered byte sequence within a QUIC connection. Loss stalls only streams that needed the lost frames — not every stream on the connection as with HTTP/2 over TCP.
- HTTP/3
- HTTP semantics mapped onto QUIC. QUIC is the transport (RFC 9000); HTTP/3 is the mapping (RFC 9114) — they are not the same specification.