Learning objective. Explain how 0-RTT sends application data in the very first flight, what it costs in replay exposure, and the rules (idempotent operations only, remembered transport parameters, server rejection) that keep it safe to use.
Spending a saved secret to skip a round trip #
A 1-RTT handshake still makes the client wait one round trip before its request is
answered (§2.1). 0-RTT removes even that wait by reusing a secret from a
previous connection. When a server issues a TLS session ticket (a pre-shared key, or
PSK), a returning client can derive early keys from it and send application data in
the same flight as its ClientHello (protected by 0-RTT packets) before the new
handshake completes ([RFC 9001 §4.6]). The server, recognizing the PSK, derives the same
early keys, accepts the early_data, and can answer immediately.
It is easy to assume the first flight is "only CRYPTO frames," but that is not how 0-RTT works.
The ClientHello does travel in a CRYPTO frame, but CRYPTO frames may appear only in Initial,
Handshake, and 1-RTT packets, never in 0-RTT packets ([RFC 9000 §12.4], Table 3). The
application request rides in a STREAM frame (allowed only in 0-RTT and 1-RTT packets) inside a
separate 0-RTT packet — a long-header packet of type 0x01 ([RFC 9000 §17.2.3]). The two
packets are coalesced into the same UDP datagram(s) ([RFC 9000 §12.2]): an Initial packet
carrying the CRYPTO/ClientHello, followed by a 0-RTT packet carrying the STREAM/request. What lets
the client encrypt that request before the handshake is that the 0-RTT packet is protected with
keys derived from the remembered PSK, not from anything the server sends in this connection:
"QUIC uses 0-RTT packets to carry early data" ([RFC 9001 §4.6.1]). So the request is real
application data in a STREAM frame; it simply travels in its own packet type alongside the
handshake, not within it.
The catch: 0-RTT is replayable #
0-RTT trades a guarantee for the round trip. Because the early data is protected by keys derived entirely from the old PSK (with no fresh input from the server), an attacker who captures the 0-RTT flight can replay it to the same or a different server, which will derive the same keys and process it again ([RFC 9001 §9.2]). Ordinary 1-RTT data is safe from this; 0-RTT is not, and no purely cryptographic fix removes the exposure.
The consequence is a hard rule: only send idempotent operations in 0-RTT. A replayed
GET is harmless; a replayed POST that charges a card or appends a record is not.
HTTP/3 states this directly: a client SHOULD NOT send non-idempotent requests as early
data, and endpoints MUST apply anti-replay mitigations ([RFC 9114 §10.9]). Servers back
this with defenses such as single-use tickets and bounded acceptance windows
([RFC 9001 §9.2] requires replay protections). A server may also simply reject 0-RTT:
the client then replays the same data over 1-RTT once the handshake completes, losing the
optimization but not the request.
Remembered transport parameters #
There is a subtler constraint. When the client sends 0-RTT, it does not yet have the
server's current transport parameters, so it must operate under the values it
remembered from the previous connection ([RFC 9000 §7.4.1]). To keep that safe, if a
server accepts 0-RTT it MUST NOT reduce certain limits (the flow-control and stream
limits the client is relying on) below the remembered values, and it MUST reject 0-RTT
outright if its restored configuration is incompatible. A handful of parameters (for
example original_destination_connection_id) are connection-specific and MUST NOT be
remembered at all.
0-RTT and "early 1-RTT" are different optimizations. 0-RTT data goes out in the client's first flight under keys from a past session (replayable). Early 1-RTT data goes out after the client installs 1-RTT keys but before the handshake is confirmed (§2.1). It is fully forward-secret and not replayable, but it does not save the initial round trip. Resumption without 0-RTT still helps by skipping certificate processing; see §10.4.
editorial Enable 0-RTT where the first request is
predictable and safe (a returning browser fetching GET / or a static asset), and keep
it off for anything that mutates state. Note that 0-RTT does not escape the 3×
anti-amplification limit (§8.1): until the client's address is validated, the
server still cannot send more than three times what it received, so a large 0-RTT
response may be paced by that limit rather than by 0-RTT itself. Treat 0-RTT as a latency
win for read-mostly entry points, not a general default.
Takeaways #
0-RTT is resumption pushed to its limit: real application data in the first flight, paid for with replay exposure that confines it to idempotent operations and remembered, non-reducible transport parameters. It completes the connection-establishment picture. The remaining handshake-chapter topics shift from starting a connection to keeping it: §2.4 shows how Connection IDs let a connection survive a change of address.