QUIC and HTTP/3
- QUIC (RFC 9000) is a transport protocol that runs on top of UDP in user space and bundles what TCP, TLS and HTTP/2 stream multiplexing did separately. HTTP/3 (RFC 9114) is HTTP carried over QUIC.
- Handshake RTTs before the request can leave: TCP + TLS 1.2 = 3, TCP + TLS 1.3 = 2, QUIC = 1, QUIC 0-RTT resumption = 0.
- Streams are independent: a lost packet stalls only the streams whose data it carried, not the whole connection. Order still holds within a stream, so head-of-line blocking is reduced, not abolished.
- A connection ID (not the IP and port) identifies the connection, so a phone can move from Wi-Fi to cellular without a new handshake.
- Browsers learn about HTTP/3 from an
Alt-Svcheader or anHTTPSDNS record, and must keep a TCP fallback because UDP is blocked on some networks. - Gains concentrate on lossy, mobile and high-RTT paths. On a fast wired path the win is small.
Mental model
Picture a courier service that stamps every parcel with its own tracking number and keeps delivering the rest of your order when one parcel goes missing. TCP is a single delivery truck: if one box falls off, the truck stops and everything behind it waits. The courier also knows you by an account number, not by your street address, so when you move house the deliveries keep coming without you re-registering.
Where it breaks: a courier never needs to hide the contents from the postman, but QUIC encrypts nearly everything, so the network in between can only see the outside of the box. And a courier that cannot reach you simply fails, while QUIC must always have a TCP route as a fallback.
QUIC is TCP + TLS 1.3 + HTTP/2 stream multiplexing, rewritten as one library over UDP datagrams. Think of the OS kernel's TCP as a shared library you cannot upgrade, and QUIC as a statically linked, versioned replacement that ships inside the browser or server binary. Each stream behaves like its own ordered, reliable pipe; the connection is the multiplexer that owns encryption, congestion control and flow control.
Where it breaks: a library call has no network in the middle. QUIC still has to cross NATs, firewalls and rate limiters that treat UDP differently from TCP, and its userspace packet handling costs CPU the kernel's TCP offloads avoid.
How it works
Why build it over UDP
TCP is implemented in every operating system kernel and inspected by middleboxes (NATs, firewalls, load balancers) that assume they understand it, so changing TCP on the wire is very slow. A brand new IP protocol number would be dropped by many middleboxes. UDP passes almost everywhere, and a transport written on top of it ships with the application. The IETF's QUIC invariants (RFC 8999) are the small set of properties that stay fixed across all QUIC versions so the protocol can keep changing. See UDP for the datagram layer this rests on and TCP connection setup for the handshake it replaces.
Counting round trips
A round trip time (RTT) is the time for a message to reach the other side and a reply to come back. Setup cost is counted in RTTs. We count RTTs before the client can send its request, then add one more RTT for the response to come back.
- TCP + TLS 1.2: 1 RTT for the TCP handshake, 2 RTTs for the TLS 1.2 full handshake. The request leaves after 3 RTTs.
- TCP + TLS 1.3: 1 RTT for TCP, 1 RTT for TLS 1.3 (TLS 1.3 needs one round trip, RFC 8446). The request leaves after 2 RTTs.
- QUIC: transport and TLS 1.3 handshakes travel together in the same packets. The request leaves after 1 RTT.
- QUIC 0-RTT: a client that has talked to this server before sends the request in its very first flight, with keys derived from the earlier session. The request leaves after 0 RTTs, at the price of replay risk (see the deep dive).
Worked example: time to first byte on 20 ms and 80 ms paths
Ignore DNS, server think time, certificate size, slow start and transfer time, and count only handshake plus one request round trip. First byte arrives after (handshake RTTs + 1) x RTT.
| Setup | RTTs before request | First byte at 20 ms RTT | First byte at 80 ms RTT |
|---|---|---|---|
| TCP + TLS 1.2 | 3 | 4 x 20 = 80 ms | 4 x 80 = 320 ms |
| TCP + TLS 1.3 | 2 | 3 x 20 = 60 ms | 3 x 80 = 240 ms |
| QUIC 1-RTT | 1 | 2 x 20 = 40 ms | 2 x 80 = 160 ms |
| QUIC 0-RTT | 0 | 1 x 20 = 20 ms | 1 x 80 = 80 ms |
QUIC saves exactly one RTT against TCP + TLS 1.3: 20 ms on the near path, 80 ms on the far one. That is why the benefit grows with distance: it is a fixed number of round trips, so the longer each one takes, the more it saves. For a fair 0-RTT comparison note that TLS 1.3 also has 0-RTT early data over TCP, but the TCP handshake still costs 1 RTT first, so QUIC 0-RTT remains one RTT ahead. A page that talks to several origins pays the setup once per origin.
Independent streams and loss
A QUIC stream is an ordered, reliable byte sequence with its own ID. Stream IDs encode who opened it and whether it is one-way: the two lowest bits of the ID give client or server initiator and bidirectional or unidirectional (RFC 9000, section 2.1). HTTP/3 puts each request and response on its own client-initiated bidirectional stream (RFC 9114, section 6.1).
QUIC packets carry frames that each name their stream and offset. When a packet is lost, the receiver still hands data from other streams to the application immediately; only a stream missing bytes waits for the retransmission. Over TCP, HTTP/2's streams are invisible to the kernel, which sees one ordered byte stream, so one lost segment holds back every stream behind it.
On TCP, the three held boxes are arrived but undeliverable: the data sits in the kernel's receive buffer until the gap is filled. The wait is at least about one RTT, because loss recovery needs the sender to learn of the loss (three duplicate ACKs, or a timeout, which is far longer) and resend. On an 80 ms path that is on the order of 80 ms of frozen streams. How likely is a loss to land in a page load? With 1 percent independent packet loss, a burst of 30 packets has probability 1 - 0.9930 = 1 - 0.74 = 26 percent of containing at least one loss. Under TCP, that one loss stalls every stream; under QUIC it stalls one.
Try it: pick HTTP/2, press Drop one packet, then switch to HTTP/3 and compare which streams stall and how the setup lane shrinks from 2 RTT to 1 (or 0 with resumption).
TCP connection
- First byte
- 240 ms
- Page complete
- 403 ms
- Retransmissions
- 0
- Streams stalled by loss
- 0
- Longest stall
- 0 ms
- Bytes delivered
- 60 KB
| Mode | Setup RTTs | First byte | Page complete | Retransmissions | Streams stalled |
|---|---|---|---|---|---|
| HTTP/1.1 | 2 | 240 ms | 570 ms | 0 | 0 |
| ▸ HTTP/2 | 2 | 240 ms | 403 ms | 0 | 0 |
| HTTP/3 | 1 | 160 ms | 323 ms | 0 | 0 |
Every mode moves the same 60 KB through the same pipe. Multiplexing saves round trips, it does not create bandwidth.
Simplified model: initial window 10 segments of 1,460 bytes, window grows by the segments acknowledged and halves once per lossy flight, HTTP/2 packs several streams into one segment while HTTP/3 keeps one stream per packet, ACKs are free, retransmissions are not lost again. A lost tail packet waits for a timer of max(200 ms, 2 RTT); a lost handshake flight waits 1000 ms.
Connection IDs and migration
A TCP connection is named by its 4-tuple (source IP, source port, destination IP, destination port). Change any of the four and the connection is dead. A QUIC connection is named by connection IDs chosen by each endpoint and carried in every packet (RFC 9000, section 5). The server looks up connection state by ID, so a client whose address changes can keep going.
Some details that matter: migration is started by the client (RFC 9000, section 9); a server may only suggest a preferred address. Each new path is validated with PATH_CHALLENGE and PATH_RESPONSE so a spoofed packet cannot redirect the connection (section 8.2 and 9.3). A migrating endpoint switches to a fresh connection ID on the new path so an on-path observer cannot trivially link the two (section 9.5), which is why peers hand each other spare IDs in advance with NEW_CONNECTION_ID frames.
How a browser finds HTTP/3
A client cannot assume a server speaks QUIC, and the first connection to an unknown site usually uses TCP. Three mechanisms cover the gap:
| Path | How it works | When the client learns | Cost if wrong |
|---|---|---|---|
| Alt-Svc header | An HTTP/1.1 or HTTP/2 response carries Alt-Svc: h3=":443"; ma=86400. The client caches it for ma seconds (86,400 s = 24 hours) and tries QUIC next time (RFC 7838, RFC 9114 section 3.1.1). | After the first visit, over TCP | First visit pays TCP + TLS |
| HTTPS DNS record | A DNS record of type 65 lists ALPN protocols such as alpn=h3,h2 with the address hints, so the client can go straight to QUIC (RFC 9460). | Before connecting, from DNS | One extra DNS lookup, usually in parallel with A and AAAA |
| Fallback to TCP | If the QUIC attempt gets no answer (UDP blocked, rate limited), the client uses HTTP/2 or HTTP/1.1 over TCP. RFC 9114 section 3.1: clients SHOULD attempt TCP-based versions. | On failure | Delay while waiting for QUIC to time out unless the attempts are raced |
Browsers typically race the two attempts in the style of Happy Eyeballs (RFC 8305) rather than waiting for a full QUIC timeout, but the exact policy is up to each browser, not the RFC. The ALPN token inside TLS is h3, so the same TLS stack also confirms the protocol (RFC 9114 section 3.1; ALPN is RFC 7301). The DNS side is covered in DNS records and routing.
HTTP/1.1 vs HTTP/2 vs HTTP/3
| HTTP/1.1 | HTTP/2 | HTTP/3 | |
|---|---|---|---|
| Transport | TCP | TCP | QUIC over UDP |
| TLS | Optional | Optional in the spec, TLS in browsers | Always, TLS 1.3 built into QUIC |
| Header compression | None | HPACK (RFC 7541) | QPACK (RFC 9204) |
| Head-of-line blocking | Per connection, at HTTP level | At TCP level, across all streams | Only within one stream |
| Connection migration | No | No | Yes, via connection IDs |
| Round trips before request (TLS 1.3) | TCP 1 + TLS 1 | TCP 1 + TLS 1 | 1 (0 on resumption) |
| Server push | No | Defined, rarely used | Defined (RFC 9114), rarely used |
The multiplexing and HoL detail for the first two columns lives in HTTP/1.1 and HTTP/2. Server push is specified in both newer versions, but reports say Chrome has removed it and I have not re-verified the current state of every browser, so treat it as a feature you should not rely on.
Deep dive
What is encrypted and what a middlebox sees
Almost the whole packet is protected. The payload is encrypted and authenticated with TLS 1.3 keys, and header protection also hides the packet number and some flag bits (RFC 9001, section 5.4). A passive observer sees the UDP/IP headers, the packet type, the version in long headers, the connection IDs, the packet length, and the spin bit if the endpoints enable it (RFC 9312, the manageability guidance).
- The Initial packet is encrypted with keys derived from the destination connection ID and a version-specific public salt (RFC 9001, section 5.2). Anyone on the path can derive them, so the ClientHello, including the SNI server name, is readable unless the client uses Encrypted Client Hello. This is a design choice: it lets middleboxes and servers at the edge route by name.
- The spin bit is a single bit that flips once per RTT and lets an on-path observer estimate latency. It is optional, and endpoints must disable it for at least one in every 16 network paths or connection IDs (RFC 9000, section 17.4).
- Firewalls therefore see "UDP on port 443" and nothing inside. QUIC is not invisible, only opaque.
Address validation, the 3x limit and Retry
UDP allows spoofed source addresses, so a server could be tricked into sending a large reply to a victim (see amplification on the UDP page). QUIC's rule: until a server has validated the client's address, it may send at most three times the bytes it has received from it (RFC 9000, section 8.1). Clients pad their first datagram to at least 1,200 bytes (section 14.1), so the server's first flight is capped near 3,600 bytes. If the server wants proof of address before doing any work, it answers with a Retry packet carrying a token; the client must come back with the token (section 8.1.2). Retry costs one more RTT, so servers use it only under load or attack.
0-RTT and replay
0-RTT data is encrypted with a key from a previous session, so the server can act on it immediately. The catch: an attacker can record and resend the 0-RTT flight, and the server may process it twice. TLS 1.3 cannot prevent this on its own (RFC 9001, section 9.2). HTTP/3 therefore expects clients to send only requests that are safe to repeat, such as GET, and servers to reject or delay anything else (RFC 9114, section 10.9, and RFC 8470 for 0-RTT in HTTP). 0-RTT data also has weaker forward secrecy because it uses the older session key. See TLS and HTTPS for how session resumption works.
Congestion control and loss recovery
QUIC does not drop congestion control. Loss detection and a baseline NewReno-style controller are specified in RFC 9002, which explicitly allows an endpoint to choose another algorithm such as CUBIC or BBR. The mechanics match TCP's, so congestion control reasoning carries over. Differences: every packet gets a new packet number, even a retransmission, so the sender never confuses an original with its resend, and ACKs carry the delay the receiver added, which sharpens RTT estimates. Connection-level and per-stream flow control windows are kept separately, as in HTTP/2.
QPACK instead of HPACK
HTTP/2 compresses headers with HPACK, which assumes both sides process header blocks in the same order TCP delivers them. Copy that onto independent QUIC streams and one stream's headers could depend on a table update that has not arrived, reintroducing head-of-line blocking. QPACK (RFC 9204) moves table updates onto dedicated unidirectional encoder and decoder streams, and a request can reference only entries the decoder has acknowledged unless the decoder allows some risk through SETTINGS_QPACK_BLOCKED_STREAMS (default 0). The trade is some compression efficiency for no cross-stream stalls.
Head-of-line blocking still exists inside a stream
Bytes inside one stream are delivered in order, so one large response with a lost packet still waits for that packet, and HTTP/3 request headers on the same stream wait for each other. "Fixed HoL" means between streams, not within them.
CPU cost: UDP buffers, GSO and GRO
TCP has decades of kernel and NIC offload: segmentation offload, checksum offload, and zero-copy paths. QUIC runs in user space, so each packet crosses the user-kernel boundary, is encrypted in the application, and needs a UDP socket. Modern Linux narrows the gap with UDP GSO (the UDP_SEGMENT socket option, which sends many datagrams in one call) and GRO on receive (kernel segmentation offload docs). Even so, expect QUIC to use more CPU per byte than TCP + TLS on the same hardware, and tune socket buffers because default UDP buffers are small (see UDP receive buffers).
QUIC v2 and greasing
QUIC version 2 (RFC 9369, version number 0x6b3343cf) is the same protocol with a few wire constants changed. Its stated purpose is to fight ossification and exercise version negotiation, not to add features. Greasing is the same idea in the version field: reserved versions of the form 0x?a?a?a?a (RFC 9000, section 15) are sent on purpose so middleboxes cannot assume a single version forever.
Load balancers and connection IDs
A layer 4 load balancer that hashes the 4-tuple breaks QUIC the moment a client migrates or a NAT rebinds, because the packets suddenly land on a different backend. The fix is to route on the connection ID, which means the server must encode routing information into the IDs it issues. The IETF QUIC-LB document defines how (draft-ietf-quic-load-balancers, the MsQuic README lists it as a draft, so confirm its current status before calling it an RFC). See load balancing for the general problem.
When UDP 443 is blocked
Some corporate firewalls and middleboxes block or rate-limit UDP, and QUIC cannot connect. Browsers fall back to TCP, so the site still loads, but only after the failed attempt. That is why every HTTP/3 deployment keeps HTTP/2 on TCP 443 running, and why the first visit to any new site is typically over TCP until Alt-Svc or an HTTPS record is known.
Textbook vs real world
| The textbook says | Production does | Why |
|---|---|---|
| HTTP/3 is always faster | Gains concentrate on lossy, mobile and high-RTT paths. On a 20 ms wired path the saving is a single RTT, so about 20 ms. | QUIC saves a fixed number of round trips and avoids cross-stream stalls; a fast clean path has little of either to save |
| UDP is unreliable, so QUIC is unreliable | QUIC provides reliable ordered delivery per stream with its own acknowledgements and retransmission | Reliability is a property of the protocol above UDP, not of UDP |
| QUIC replaced TCP | HTTP/3 coexists with HTTP/2 on TCP, and a TCP fallback is expected | UDP can be blocked, and most clients first learn HTTP/3 support through TCP |
| Connection migration is automatic | The client has to probe the new path, validate it and switch connection IDs | Without validation an attacker could redirect traffic |
| 0-RTT removes handshake cost | Servers accept 0-RTT only for idempotent requests | Early data can be replayed |
In production
- Google deployed its early QUIC to Chrome and its own servers starting in 2013. The SIGCOMM 2017 paper reports it carrying over 30 percent of Google's egress traffic, roughly 7 percent of internet traffic at the time, and shows the largest latency gains on the slowest connections (Langley et al., "The QUIC Transport Protocol: Design and Internet-Scale Deployment"). I did not re-read the paper's tables for exact percentages; read it for the per-percentile numbers.
- Meta wrote that more than 75 percent of its internet traffic used QUIC and HTTP/3 as of its 2020 post, using its open-source
mvfstimplementation (How Facebook is bringing QUIC to billions; mvfst). - Cloudflare serves HTTP/3 to clients by default and publishes the live HTTP/3 share of requests on Cloudflare Radar. The figure moves over time and I did not freeze a number here; open the dashboard for the current value. Their primer is HTTP/3: the past, the present, and the future.
- Fastly, Akamai and other CDNs and the major browsers support HTTP/3; caniuse tracks per-browser status.
- Microsoft maintains the open-source msquic library, a cross-platform C implementation of RFC 9000, 9001 and 9002 used as a general-purpose QUIC stack.
- Today's headers confirm it: when I ran
curl -sI https://example.comandhttps://www.google.comon 2026-10-10, both responses carriedalt-svc: h3=":443"withma=86400andma=2592000respectively.
History and evolution
- 2009 Google announces SPDY, an experiment to multiplex many requests over one TCP connection and compress headers because HTTP/1.1 opened many slow connections.
- 2012 Google begins the QUIC experiment: reduce handshake round trips and TCP head-of-line blocking by building a new transport over UDP. It reaches Chrome and Google servers in 2013 (as the SIGCOMM 2017 paper describes).
- 2015 HTTP/2 (RFC 7540, based on SPDY) is published. What it left unsolved: all streams share one TCP connection, so a lost packet stalls every stream.
- 2016 The IETF forms a QUIC working group to standardise a version built on TLS 1.3 rather than Google's custom crypto.
- 2021 RFC 8999 (invariants), RFC 9000 (transport), RFC 9001 (TLS) and RFC 9002 (loss recovery) are published in May.
- 2022 HTTP/3 becomes RFC 9114 in June, with QPACK as RFC 9204. HTTP/2 is refreshed as RFC 9113.
- 2023 QUIC version 2 (RFC 9369) is published to exercise version negotiation and resist ossification.
- Now Large CDNs, major browsers and big web properties run HTTP/3 at scale; Cloudflare Radar publishes the live share of requests over HTTP/3.
- Next WebTransport and CONNECT-UDP/MASQUE (RFC 9298) carry more traffic over QUIC; multipath QUIC and QUIC-LB are in the IETF pipeline.
Hands-on
macOS and Linux unless flagged.
See the advertisement. Look for alt-svc in a response from a server that supports HTTP/3:
That is the real output as I ran it on 2026-10-10. h3 is the protocol, :443 is the UDP port to try, and ma=86400 is the cache lifetime in seconds. A big site may advertise several tokens and longer lifetimes, for example h3-29 (an old draft) alongside h3 with ma=2592000 (30 days), as www.google.com did when I checked.
Ask for HTTP/3 directly. This needs a curl built with HTTP/3:
The macOS system curl (8.7.1 on my machine) does not list HTTP3 in its features, so --http3 will fail there; Homebrew's curl may include it. On a build with HTTP/3 expect the first status line to start with HTTP/3. I did not capture that output on this machine.
Look at the DNS hint. An HTTPS record (type 65) announces the protocols:
macOS ships dig 9.10.6, which does not know the type by name and prints raw hex instead; ask with dig +short TYPE65 cloudflare.com. The record I got for cloudflare.com began 0001 00 0001 0006 02 6833 02 6832, which decodes as priority 1, target ., and an alpn list of h3 and h2. A newer BIND dig prints it as 1 . alpn="h3,h2" ipv4hint=....
Watch the packets. QUIC is plain UDP on the wire:
What to look for: UDP packets to and from port 443, with the client's first datagram at least 1,200 bytes long (the padding rule), and nothing readable in the payload. You can see alt-svc over TCP and then UDP 443 on the next visit. The exact lengths and interface names vary by browser and machine.
In the browser. Open DevTools, Network, right-click the column header and enable Protocol. Requests served over HTTP/3 show h3. chrome://net-export records a log if you need deeper detail.
Gotchas
Interview questions
Show answer
TCP is embedded in operating system kernels and inspected by middleboxes that assume they understand it, so it is almost impossible to change on the wire. A new IP protocol number would be dropped by many NATs and firewalls. UDP passes nearly everywhere and QUIC can run in user space, so it ships with the application and can keep evolving. QUIC then rebuilds reliability, congestion control and encryption on top.
Follow-ups:
- Could it have used a new IP protocol number? In theory, but ossification means many middleboxes drop unknown protocols, so deployment would stall.
- What is the cost of user space? More CPU per byte than kernel TCP with offloads, partly offset by UDP GSO and GRO.
Show answer
HTTP/2 multiplexes streams over one TCP connection, but TCP delivers one ordered byte stream, so one lost packet stalls every stream behind it. QUIC keeps streams independent: each packet's frames name their stream, and a loss only blocks streams that had data in the missing packet. QPACK also avoids cross-stream header dependencies.
Follow-ups:
- Does head-of-line blocking still exist within a stream? Yes, bytes inside a stream are delivered in order.
- Is any HoL left between streams? QPACK can block a stream if a header refers to an unacknowledged table entry and the decoder allows it.
Show answer
Count handshake round trips before the request, then one more for the response. TCP + TLS 1.3 needs 2 before the request, so first byte at 3 RTT. QUIC combines the transport and TLS handshakes, so 1, first byte at 2 RTT. QUIC 0-RTT resumption sends the request in the first flight, so 0 and first byte at 1 RTT. TCP + TLS 1.2 would be 3 and 4.
Follow-ups:
- Why is 0-RTT risky? Early data can be replayed, so only idempotent requests are safe.
- Does TLS over TCP have 0-RTT? Yes with TLS 1.3 early data, but the TCP handshake still costs a round trip first.
Show answer
Two ways. The server can send an Alt-Svc: h3=":443"; ma=86400 header on an HTTP/1.1 or HTTP/2 response, which the client caches and uses on the next visit. Or DNS can carry an HTTPS record listing alpn=h3,h2, so the client knows before the first connection. Either way, the client keeps TCP ready as a fallback and often races the two.
Follow-ups:
- What if UDP is blocked? The QUIC attempt fails or times out and the client uses HTTP/2 or HTTP/1.1 over TCP (RFC 9114, section 3.1).
- How long does Alt-Svc last? The
mavalue in seconds; the default is 24 hours.
Show answer
Migration lets a QUIC connection continue after a client's IP address or port changes, such as moving from Wi-Fi to cellular. It works because the connection is identified by connection IDs carried in each packet, not by the 4-tuple. The client probes the new path, the server validates it with PATH_CHALLENGE and PATH_RESPONSE, and the same keys and streams continue.
Follow-ups:
- What does a load balancer need? It must route on the connection ID, so servers encode routing data into the IDs they issue; QUIC-LB is the draft for that.
- Why switch connection IDs on the new path? So an observer cannot easily link the old and new paths.
Show answer
Costs: higher CPU per byte because processing is in user space and offload support for UDP is less mature than for TCP, tuning of UDP socket buffers, rate limiting or blocking of UDP on some networks, and NAT timeouts for idle UDP. Gains are largest on lossy, mobile or long-RTT clients. Decide by measuring: enable HTTP/3 alongside HTTP/2, compare latency percentiles and error rates by network type and client, and watch server CPU.
Follow-ups:
- Why is amplification a concern? UDP allows spoofing; QUIC limits servers to 3x the bytes received until the address is validated.
- What about NAT rebinding? A NAT may assign a new port after idle, which looks like migration; QUIC handles it, but a 4-tuple load balancer will not.
Show answer
UDP and IP headers, packet sizes and timing, connection IDs, the version in long headers, and the spin bit if enabled. The Initial packet's keys are derived from public values, so the ClientHello, including SNI, is readable unless the client uses Encrypted Client Hello. Everything after that, including all HTTP data, is encrypted.
Follow-ups:
- What is the spin bit for? It flips once per RTT so observers can measure latency; endpoints must disable it on at least one in 16 network paths (or connection IDs).
- What does ECH change? It encrypts the inner ClientHello so SNI is hidden from observers.
Show answer
Before a client's address is validated, the server may send no more than three times the bytes it has received. Clients pad their first datagram to at least 1,200 bytes, which bounds the server's first reply. A server under load can send a Retry packet with a token, forcing the client to prove it can receive at its address.
Follow-ups:
- What does Retry cost? One additional round trip.
- Why does TCP not have this problem? The three-way handshake already requires the source to answer.
Show answer
They share the connection's congestion controller and its congestion window. Loss recovery is per stream in the sense that a lost packet only delays the streams whose frames it carried, but a loss event still shrinks the window for the whole connection, so every stream sends more slowly afterwards. Flow control is kept at both connection and stream level, so a slow reader can also hold back the connection total. "No head-of-line blocking" means no cross-stream delivery stalls, not independent bandwidth.
Follow-ups:
- Does QUIC use a different congestion algorithm from TCP? The baseline in RFC 9002 is NewReno-style, and an endpoint may choose CUBIC or BBR, so the reasoning from congestion control carries over.
- Is HTTP/3 then always better on lossy links? For many concurrent streams it avoids the cross-stream stall, but it still pays the window cut and in-stream ordering.
Show answer
HPACK assumes header blocks are processed in the order TCP delivers them. On independent QUIC streams, one stream's headers could reference a table update that arrived on another stream and has not been processed yet, which would bring head-of-line blocking back. QPACK (RFC 9204) sends table updates on dedicated unidirectional encoder and decoder streams, and a request may reference only entries the decoder has acknowledged, unless the decoder allows some blocked streams through SETTINGS_QPACK_BLOCKED_STREAMS, whose default is 0. The price is slightly worse compression.
Follow-ups:
- Is any head-of-line blocking left? Yes: a stream can block if it references an unacknowledged entry and the decoder allows blocking, and bytes inside one stream stay ordered.
- How does the HPACK side work? A static table plus a per-connection dynamic table and Huffman coding, covered on the HTTP/2 page.
Fun facts
- QUIC began as "Quick UDP Internet Connections", but RFC 9000 says plainly that "QUIC is a name, not an acronym" (section 1.2).
- QUIC version 2 uses the version number
0x6b3343cf, deliberately not the number 2, to avoid ossification. - Version numbers matching
0x?a?a?a?aare reserved for greasing: they exist so implementations learn to cope with unknown versions. - The QUIC spin bit is optional and endpoints must turn it off on at least one in 16 network paths (or connection IDs) on purpose so that disabled connections remain common.
Cheat sheet
- RFC 9000 / 9001 / 9002 - QUIC transport / TLS / loss recovery; RFC 9114 HTTP/3; RFC 9204 QPACK
- 3 / 2 / 1 / 0 RTT - before the request: TCP + TLS 1.2 / TCP + TLS 1.3 / QUIC / QUIC 0-RTT
- 1,200 bytes - minimum size of a client's first QUIC datagram
- 3x - amplification limit before address validation
- ma=86400 - Alt-Svc cache lifetime in seconds (24 hours), also the default
- h3 - the ALPN token; UDP port 443 by default