Skip to main content

QUIC and HTTP/3

TL;DR
  • 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-Svc header or an HTTPS DNS 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​

ANALOGYEveryday

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.

ANALOGYFor engineers

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.

HTTP/2HTTP/3HTTP/2TLS 1.3TCPIPHTTP/3QUICUDPIPstreams, HPACK headersencryption, own handshakeone ordered byte streamaddressing and routingstreams, QPACK headersstreams + recovery + CC + TLS 1.3ports and checksum onlyaddressing and routingTCP and IP are in the OS kerneland understood by middleboxesQUIC is a library inside the app;only UDP and IP are in the kernel
HTTP/2 stacks three separate layers: HTTP/2, TLS, TCP. HTTP/3 puts streams, loss recovery, congestion control and the TLS 1.3 handshake inside one QUIC layer that sits on UDP. TCP lives in the kernel; QUIC lives in the application's library.

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).
TCP + TLS 1.2TCP + TLS 1.3QUIC 1-RTTQUIC 0-RTT (resumed)clientserverSYNSYN-ACKClientHelloServerHello+certKey exch + FinServer FinishedGETresponseclientserverSYNSYN-ACKClientHelloServerHello..FinFinished + GETresponseclientserverInitial: CHelloInitial+HandshakeHShake Fin + GETresponseclientserverCHello + GETSHello + responserequest leaves at 3 RTTfirst byte back at 4 RTTrequest leaves at 2 RTTfirst byte back at 3 RTTrequest leaves at 1 RTTfirst byte back at 2 RTTrequest leaves at 0 RTTfirst byte back at 1 RTT
Four ladders, same request. Heavy arrows are the HTTP request. Each downward slant is time passing as a message crosses the network. Each pair of arrows (out and back) is one round trip.

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.

SetupRTTs before requestFirst byte at 20 ms RTTFirst byte at 80 ms RTT
TCP + TLS 1.234 x 20 = 80 ms4 x 80 = 320 ms
TCP + TLS 1.323 x 20 = 60 ms3 x 80 = 240 ms
QUIC 1-RTT12 x 20 = 40 ms2 x 80 = 160 ms
QUIC 0-RTT01 x 20 = 20 ms1 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.

TCP (HTTP/2): one ordered byte streamQUIC (HTTP/3): ordered per streampkt 1pkt 2pkt 3pkt 4pkt 5pkt 6pkt 7 (resend)pkt 1pkt 2pkt 3pkt 4pkt 5pkt 6pkt 7 (resend)stream Astream Bstream Cstream Astream Bstream CA1 okB1 okC1 lostA2 heldB2 heldC2 heldC1 okA1 okB1 okC1 lostA2 okB2 okC2 heldC1 okA2, B2, C2 all wait for packet 7, then release togetherA2 and B2 are delivered on arrival; only C waits for C1
Six packets carry three streams A, B, C; packet 3 (C1) is lost and resent as packet 7. TCP holds everything behind the gap. QUIC delivers A2 and B2 at once and holds only C2. Words inside each box carry the state, not just the outline.

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).

in flightdeliveredlostretransmitted copyarrived, blocked behind a loss
one TCP connection
Path and page
80 ms
50 Mbit/s
30
2 KB
0 %

TCP connection

TCP0 ms to 80 ms
TLS 1.380 ms to 160 ms
Flight 1: 10 packets, leaves at 200 ms
12345678910
Flight 2: 20 packets, leaves at 280 ms
1112131415161718192021222324252627282930
Flight 3: 12 packets, leaves at 360 ms
13691114171922252830
First byte
240 ms
Page complete
403 ms
Retransmissions
0
Streams stalled by loss
0
Longest stall
0 ms
Bytes delivered
60 KB
Finish time per streams1 400s2 400s3 400s4 400s5 400s6 401s7 401s8 401s9 401s10 401s11 401s12 401s13 401s14 401s15 401s16 401s17 402s18 402s19 402s20 402s21 402s22 402s23 402s24 402s25 402s26 402s27 402s28 403s29 403s30 403
Same path, same 60 KB, same loss seed, all three modes
ModeSetup RTTsFirst bytePage completeRetransmissionsStreams stalled
HTTP/1.12240 ms570 ms00
▸ HTTP/22240 ms403 ms00
HTTP/31160 ms323 ms00

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.

t = 0 ms. Client starts TCP + TLS 1.3.
event 0 / 5

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.

Wi-FiCellularServer192.0.2.10:51000198.51.100.7:40000203.0.113.5:443state is keyed byconnection ID:keys, streams,congestion window1 data, destination CID = A12 Wi-Fi link drops3 PATH_CHALLENGE, new CID = A24 PATH_RESPONSE: path validated5 same streams resume, no handshakeThe server matches packets to the connection by ID, not by IP and port.
The client leaves Wi-Fi and continues on cellular. The server validates the new path with a challenge and response, then keeps using the same keys and streams. Addresses are documentation examples (RFC 5737).

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:

PathHow it worksWhen the client learnsCost if wrong
Alt-Svc headerAn 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 TCPFirst visit pays TCP + TLS
HTTPS DNS recordA 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 DNSOne extra DNS lookup, usually in parallel with A and AAAA
Fallback to TCPIf 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 failureDelay 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.1HTTP/2HTTP/3
TransportTCPTCPQUIC over UDP
TLSOptionalOptional in the spec, TLS in browsersAlways, TLS 1.3 built into QUIC
Header compressionNoneHPACK (RFC 7541)QPACK (RFC 9204)
Head-of-line blockingPer connection, at HTTP levelAt TCP level, across all streamsOnly within one stream
Connection migrationNoNoYes, via connection IDs
Round trips before request (TLS 1.3)TCP 1 + TLS 1TCP 1 + TLS 11 (0 on resumption)
Server pushNoDefined, rarely usedDefined (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 saysProduction doesWhy
HTTP/3 is always fasterGains 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 unreliableQUIC provides reliable ordered delivery per stream with its own acknowledgements and retransmissionReliability is a property of the protocol above UDP, not of UDP
QUIC replaced TCPHTTP/3 coexists with HTTP/2 on TCP, and a TCP fallback is expectedUDP can be blocked, and most clients first learn HTTP/3 support through TCP
Connection migration is automaticThe client has to probe the new path, validate it and switch connection IDsWithout validation an attacker could redirect traffic
0-RTT removes handshake costServers accept 0-RTT only for idempotent requestsEarly 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 mvfst implementation (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.com and https://www.google.com on 2026-10-10, both responses carried alt-svc: h3=":443" with ma=86400 and ma=2592000 respectively.

History and evolution​

HISTORY
  • 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:

curl -sI https://example.com | grep -i alt-svc
alt-svc: h3=":443"; ma=86400

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:

curl --version | grep -i http3
curl -sI --http3 https://cloudflare.com

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:

dig HTTPS cloudflare.com

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:

sudo tcpdump -n -i any udp port 443

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​

GOTCHAS4
QUIC replaced TCP
0-RTT is free speed
UDP means no congestion control
QUIC is invisible to firewalls

Interview questions​

INTERVIEWWhy does HTTP/3 use UDP instead of its own IP protocol or TCP?junior
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.
INTERVIEWHow does HTTP/3 fix head-of-line blocking?mid
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.
INTERVIEWHow many RTTs to the first byte for TCP + TLS 1.3, QUIC and QUIC 0-RTT?mid
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.
INTERVIEWHow does a browser discover that a site supports HTTP/3?mid
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 ma value in seconds; the default is 24 hours.
INTERVIEWWhat is connection migration and what makes it work?mid
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.
INTERVIEWWhere is QUIC slower or worse than TCP, and how do you decide whether to enable it?senior
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.
INTERVIEWWhat can a network observer see on a QUIC connection?senior
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.
INTERVIEWHow does QUIC defend against amplification and spoofed addresses?mid
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.
INTERVIEWQUIC removes head-of-line blocking between streams. What do the streams still share?senior
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.
INTERVIEWWhy does HTTP/3 need QPACK instead of HPACK?senior
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​

FACTSQUIC trivia
  • 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?a are 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​

NUMBERSQUIC and HTTP/3 numbers to remember
  • 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
DECISIONEnable HTTP/3?
Many mobile or high-RTT users→Enable it, expect the biggest gain
Mostly fast wired users→Enable it, expect small gains
UDP blocked for part of your audience→Enable it, but keep HTTP/2 as the fallback
Strict firewall or CPU-bound edge→Measure CPU and latency before enabling widely
Pick this when: you keep TCP-based HTTP/2 running and can measure the difference