TLS 1.3 Zero Round Trip Resumption and the Replay Risk It Hides
0-RTT shaves a round trip off reconnects, but ship it wrong and you invite replayed POSTs; here is exactly where the line is.
TLS 1.3 cut the handshake for a fresh connection from two round trips to one, which is already a meaningful latency win. Then it added a mode that goes further: 0-RTT, zero round-trip time, which lets a returning client send application data in its very first message, before the server has said anything back. For a user reconnecting to a service they have visited before, that shaves a full round trip off the connection, and on a high-latency mobile link that is real, perceptible speed.
It is also the one TLS 1.3 feature you can turn on carelessly and open a genuine security hole. 0-RTT trades away a specific guarantee that the rest of TLS provides, and if you send the wrong kind of request over it, an attacker who captured that request can replay it and make it happen again. The feature is worth using. It is not worth using without understanding exactly where the line is, because the protocol itself does not protect you from crossing it.
What 0-RTT actually does
In a normal TLS 1.3 handshake, the client and server exchange messages to agree on keys before any application data flows. 0-RTT lets a client that has connected before reuse a pre-shared key from the previous session to encrypt and send data immediately, bundled with the first handshake message. The server can process that "early data" right away. The client did not wait for a round trip, so the request arrives a full network round trip sooner.
The mechanism that makes this possible (reusing key material from a prior session) is also the source of its weakness. Early data sent this way does not have the same security properties as the rest of the connection, and the two specific properties it gives up are the ones that matter.
The two guarantees you are trading away
0-RTT early data is weaker than normal TLS data in two distinct ways, and both come directly from the TLS 1.3 specification, not from a particular implementation's bug.
It is not forward secret. Normal TLS 1.3 data has forward secrecy, meaning if the server's long-term key is compromised later, past recorded traffic still cannot be decrypted. 0-RTT early data does not have this property to the same degree, because it is encrypted with key material derived from the earlier session rather than from a fresh ephemeral exchange.
It is replayable. This is the big one. An attacker who captures a 0-RTT message can resend it to the server, and the server, having no protocol-level way to know it is a replay, will process it again as if it were a new, valid request. The TLS 1.3 specification is explicit that it does not provide replay protection for 0-RTT data at the protocol level. It proposes countermeasures, but it leaves implementing them to the application and the server.
Sit with what replayability means. If the captured request was "GET /home," replaying it does nothing harmful, the attacker just causes the server to send the homepage again, to nobody. If the captured request was "POST /transfer, move $500," replaying it moves another $500. The danger of 0-RTT is entirely about whether the request changes state.
The line: idempotent requests only
This is the rule that keeps 0-RTT safe: only send requests over 0-RTT that are safe to execute more than once. In HTTP terms, that means idempotent and safe methods, GET and HEAD, the requests that read data without changing it. A GET replayed ten times produces ten identical reads and zero side effects, so a replay is harmless. That is exactly the kind of request 0-RTT is for.
Never send state-changing requests over 0-RTT. POST, PUT, DELETE, PATCH, anything that creates, modifies, charges, or deletes, must not travel as early data, because a replay of one of those does the state change again. A payment, an order, a transfer, a "send message" each becomes a thing an attacker can make happen twice by replaying a packet they captured once.
The good news is that this is a well-understood line and the major web servers and reverse proxies that support 0-RTT let you enforce it. You can configure them to accept early data only for safe methods and to refuse or downgrade anything else to a normal (non-0-RTT) round trip. The latency win still applies where it is safe (the reads, which are most page-load traffic) and the dangerous requests pay one extra round trip, which is the correct trade.
Defence in depth, because configuration drifts
Restricting 0-RTT to GET and HEAD at the server is the primary defence, but relying on it alone is fragile, because a configuration change, a new endpoint, or a proxy in front that handles early data differently can quietly let a state-changing request through. The safer posture is to also make your state-changing endpoints replay-resistant on their own, independent of TLS, so a replay does nothing even if one slips through.
The countermeasures that help, layered with the method restriction:
- Idempotency keys on mutating requests. The same pattern that protects against duplicate payment webhooks protects here: a client-generated unique key per operation, recorded server-side, so processing the same request twice is a no-op. This makes a replayed POST harmless even if it reaches the application, because the second execution is recognised and dropped, exactly the discipline behind keeping agent tool calls idempotent before they double-charge a customer.
- Replay caches on the server, tracking the 0-RTT identifiers it has seen so a repeated one is rejected. In a distributed setup this cache has to be shared across servers, or an attacker simply replays to a different server that has not seen the original. The same edge-config thinking applies when you serve unlimited subdomains from one Cloudflare origin certificate and have to keep early-data policy consistent across all of them.
- Binding early data to context, the client IP, the SNI, the ALPN, so a captured packet cannot be trivially reused against a different connection or service.
- Limiting the size and time window of accepted early data, narrowing the window in which a replay is useful.
The honest framing: the TLS layer gives you a fast path with a sharp edge, and the application layer is where you make the edge safe. The single most important rule is the method restriction. The idempotency keys are what turn "we restricted it correctly" into "even if we didn't, nothing bad happens," and that second layer is what you want for anything that touches money or data.
When to reach for it, and when not to
0-RTT earns its place on high-latency connections where reconnections are frequent and most traffic is reads, which describes a lot of mobile and global web traffic, the same audience for whom what most software teams get wrong about shipping to African markets matters most. The latency it saves is real and felt, and it stacks with the other connection-level wins like moving to HTTP/3 and QUIC to kill head-of-line blocking and cutting resolver latency with anycast DNS. The decision is not "0-RTT, yes or no," it is "0-RTT for the safe reads, normal handshake for the state changes, and idempotency on the mutations regardless."
Get that split right and you take the speed without taking the risk. Get it wrong (enable 0-RTT broadly and let a POST go over it) and you have built a replayable endpoint that an attacker can trigger by resending a packet. The difference is entirely in the configuration and the application-layer safeguards, which is exactly the kind of detail that lives in careful networking and TLS setup, and where service-to-service traffic is involved, locking it down with mutual TLS that rotates itself. It is also exactly the kind of thing a security audit checks: not just that TLS 1.3 is on, but that 0-RTT, if enabled, is restricted to safe methods and your mutations are replay-resistant underneath. Speed and safety are not in conflict on this feature. The line between them is just precise, and the protocol leaves it to you to hold. If you are considering 0-RTT or already running it, confirming you are on the right side of that line is worth an hour.






