How QUIC works
How QUIC, the transport protocol under HTTP/3, combines the jobs of TCP and TLS on top of UDP, from its packets, connection IDs and streams to its one-round-trip handshake, loss recovery and connection migration.
For decades, a secure web connection has been built from two separate protocols: TCP for reliable delivery and TLS for encryption, with TLS running on top of TCP. QUIC replaces both with a single protocol that runs on top of UDP. It was designed to make connections faster to set up, to stop one lost packet from delaying unrelated data, and to keep connections alive when a device changes networks. It is the foundation of HTTP/3.
This article explains why QUIC exists, how its packets and streams work, how it sets up an encrypted connection in one round trip, and how it recovers from loss.
Why a new transport protocol
QUIC began at Google: RFC 9000 cites Jim Roskind’s early design document from 2013, and Google deployed the protocol before it was standardized. The IETF then standardized a reworked version, published in May 2021 as RFC 9000 (the transport), RFC 9001 (its use of TLS) and RFC 9002 (loss detection and congestion control). In the standard, QUIC is a name, not an acronym.
Three problems with TCP and TLS motivated it:
- Slow connection setup. TCP needs one round trip for its handshake and TLS 1.3 at least one more, so the first request leaves only after two round trips. On a mobile network with a round-trip time of 100 milliseconds, that is a fifth of a second before anything useful happens.
- Head-of-line blocking. TCP delivers one stream of bytes, strictly in order. When HTTP/2 sends many requests over one TCP connection, a single lost packet holds back all of them until it is retransmitted, even the ones whose data has already arrived.
- Ossification. Firewalls, NAT devices and other equipment on the path inspect TCP headers and often drop anything unfamiliar. As a result, even small improvements to TCP take many years to become usable. And because TCP lives in the operating system’s kernel, changing it means updating operating systems.
QUIC answers all three. It builds on UDP, which almost every network passes and which adds nothing but port numbers, and implements everything else itself, usually inside the application or a library. And it encrypts almost all of its own headers, so that devices on the path cannot depend on them and the protocol can keep evolving.
Packets and frames
A QUIC connection exchanges packets, each carried in a UDP datagram; one datagram can hold several packets. Each packet has a small header and an encrypted payload, and the payload is a sequence of frames, the units that carry everything QUIC does:
| Frame | Purpose |
|---|---|
STREAM |
Carries application data for one stream |
ACK |
Acknowledges received packets, as ranges of packet numbers |
CRYPTO |
Carries the TLS handshake messages |
MAX_DATA, MAX_STREAM_DATA |
Raises the flow control limits for the connection or a stream |
MAX_STREAMS |
Allows the peer to open more streams |
RESET_STREAM, STOP_SENDING |
Abandons a stream in one direction |
NEW_CONNECTION_ID |
Hands the peer another connection ID |
PATH_CHALLENGE, PATH_RESPONSE |
Checks that a new network path works |
PING, PADDING |
Keeps a connection alive, or makes a packet larger |
CONNECTION_CLOSE |
Ends the connection, with an error code |
Because the frames are encrypted, an observer cannot even tell which kinds of frames a packet contains.
QUIC uses two header formats. During the handshake, packets have a long header, which carries the QUIC version and the connection IDs of both sides. Once the connection is set up, packets switch to a short header, which carries only the destination connection ID and the packet number. Even the packet number is hidden, with a separate step called header protection.
Connection IDs
A TCP connection is identified by its addresses and ports. If any of them changes, the connection is lost. QUIC identifies a connection by connection IDs instead: each side chooses IDs, up to 20 bytes long, that the other side puts in the packets it sends, so that the receiver can find the connection even if the packets suddenly arrive from a different address.
Each side can give the other several connection IDs in advance, with NEW_CONNECTION_ID frames.
When a client moves to a new network, it switches to a fresh connection ID at the same time, so
that an observer cannot link the old path and the new one to the same connection.
This makes connection migration possible. When a phone leaves Wi-Fi and continues on mobile
data, its address changes, but the server recognizes the connection by its connection ID. Before
sending much data to the new address, the server checks that the address really belongs to the
client: it sends a PATH_CHALLENGE frame with random data, which the client must echo in a
PATH_RESPONSE. The same mechanism handles the more common case where a NAT device silently
gives a client a new port.
Streams
Inside a connection, QUIC carries any number of streams: independent, ordered byte streams, like small TCP connections that share one handshake and one congestion controller. Either side can open a stream simply by sending data on it; no extra round trip is needed.
Streams are numbered, and the two lowest bits of the number say who opened it and whether it carries data in one direction or both:
| Lowest two bits | Stream type | First IDs |
|---|---|---|
00 |
Opened by the client, both ways | 0, 4, 8, … |
01 |
Opened by the server, both ways | 1, 5, 9, … |
10 |
Opened by the client, one way | 2, 6, 10, … |
11 |
Opened by the server, one way | 3, 7, 11, … |
QUIC delivers each stream’s data in order, but streams do not wait for each other. When a packet is lost, only the streams whose data it carried must wait for the retransmission; the others keep flowing. HTTP/3 sends each request and its response on its own client-opened stream, so a lost packet delays only the requests it belonged to.
Flow control works at two levels: a limit for each stream (MAX_STREAM_DATA) and a limit for the
whole connection (MAX_DATA). A third limit, MAX_STREAMS, caps how many streams the peer may
open, which protects the receiver’s memory.
The handshake
QUIC does not use TLS records. It uses only the TLS 1.3 handshake and its key schedule, and
carries the handshake messages in its own CRYPTO frames. With the keys that TLS derives, QUIC
then encrypts its packets itself. The transport settings, such as flow control limits and how many
streams may be opened, travel inside the TLS handshake in a quic_transport_parameters
extension, so that they are authenticated together with everything else.
A connection’s packets fall into three groups, each protected with different keys and numbered separately:
- Initial packets carry the ClientHello and ServerHello. Their keys are derived from the connection ID the client chose and a constant from the specification, so anyone who sees the packets can decrypt them. They protect against tampering by devices that do not understand QUIC, not against eavesdroppers.
- Handshake packets carry the rest of the TLS handshake, protected by the TLS handshake keys.
- 1-RTT packets carry application data, protected by the TLS application keys.
The client can send its first request together with its Finished message, one round trip after it started. A client that has talked to the server before can even send its request in its very first flight as 0-RTT data, with the same caveat as in TLS: early data can be replayed by an attacker, so it suits only requests that are safe to repeat.
Protection against amplification
A server answers a small Initial packet with a much larger flight that includes its certificate. An attacker could abuse that: send small packets with a forged source address and let the server flood the victim at that address. QUIC prevents this in two ways. A client’s first Initial packet must be padded to at least 1,200 bytes, and until the server has confirmed the client’s address, it may send at most three times as many bytes as it has received. A server under attack can also answer with a Retry packet containing a token, which the client must send back, proving that it receives packets at its claimed address.
Loss detection and recovery
QUIC’s reliability works differently from TCP’s in one important detail: packet numbers are never reused. TCP retransmits the same byte ranges with the same sequence numbers, so when an acknowledgment arrives, the sender cannot tell whether it refers to the original transmission or the retransmission, which makes round-trip measurements ambiguous. QUIC numbers every packet anew, and data that must be retransmitted is sent in new frames inside a new packet with a new number.
ACK frames list the received packets as ranges, so a single frame can describe many gaps, and they report how long the receiver waited before acknowledging, so that the sender can measure the round-trip time precisely. A sender declares a packet lost when a packet sent three numbers later has been acknowledged, or when a later packet has been acknowledged and enough time has passed, slightly more than one round-trip time (RFC 9002). If no acknowledgment arrives at all, a probe timeout makes the sender send one or two packets to provoke one.
For congestion control, RFC 9002 describes an algorithm similar to TCP’s NewReno, with the same slow start, congestion avoidance and response to loss. Because QUIC usually runs inside the application, implementations can easily use others, such as CUBIC or BBR, and update them with the application instead of waiting for the operating system.
What else QUIC brings
- Clean shutdowns and stateless resets. A
CONNECTION_CLOSEframe ends a connection with an error code. A server that has lost its state, for example after a restart, can still end a connection with a stateless reset: a packet containing a secret token that the server handed out earlier. - Datagrams. An extension (RFC 9221) adds unreliable datagrams to a QUIC connection, for data where a late copy is worthless, such as audio and video in real time. They enjoy QUIC’s encryption and congestion control without being retransmitted.
- Versions. The parts of QUIC that every version must keep, such as how version negotiation works, are fixed in RFC 8999. QUIC version 2 (RFC 9369) changes almost nothing but the version number and a few constants, mainly so that devices on the path do not start to depend on version 1 alone.
Costs and trade-offs
QUIC is not free:
- Some networks block or limit UDP. Clients therefore keep TCP as a fallback: when a QUIC connection cannot be established, HTTP clients should use a TCP-based version of HTTP instead (RFC 9114).
- More processing. Every packet is encrypted individually, and the work usually happens in the application rather than in the operating system, where TCP has been optimized for decades.
- Less visibility for network operators. Encrypted headers stop operators from measuring connections the way they could with TCP. QUIC offers an optional spin bit, which lets a passive observer estimate the round-trip time without seeing anything else (RFC 9312).
Key points
- QUIC combines the jobs of TCP and TLS in one protocol on top of UDP, and encrypts almost all of its headers so it can keep evolving.
- Connection IDs, not addresses, identify a connection, which allows connection migration between networks.
- Streams are independent: a lost packet delays only the streams whose data it carried.
- The TLS 1.3 handshake runs inside QUIC, so a new connection needs one round trip, and a resumed one can send data immediately with 0-RTT.
- Packet numbers are never reused, which makes acknowledgments unambiguous and loss detection precise.
Sources
- QUIC: A UDP-Based Multiplexed and Secure Transport (RFC 9000)J. Iyengar, M. Thomson, RFC Editor, 2021Accessed
- Using TLS to Secure QUIC (RFC 9001)M. Thomson, S. Turner, RFC Editor, 2021Accessed
- QUIC Loss Detection and Congestion Control (RFC 9002)J. Iyengar, I. Swett, RFC Editor, 2021Accessed
- Version-Independent Properties of QUIC (RFC 8999)M. Thomson, RFC Editor, 2021Accessed
- HTTP/3 (RFC 9114)M. Bishop, RFC Editor, 2022Accessed
- An Unreliable Datagram Extension to QUIC (RFC 9221)T. Pauly, E. Kinnear, D. Schinazi, RFC Editor, 2022Accessed
- QUIC Version 2 (RFC 9369)M. Duke, RFC Editor, 2023Accessed
- Applicability of the QUIC Transport Protocol (RFC 9308)M. Kühlewind, B. Trammell, RFC Editor, 2022Accessed
- Manageability of the QUIC Transport Protocol (RFC 9312)M. Kühlewind, B. Trammell, RFC Editor, 2022Accessed