TLS and QUIC can reduce repeat-connection latency by allowing a client to send application data before a new handshake completes. That 0-RTT path changes the threat model: early data can be replayed.
The invariant is:
No request may enter early data unless executing it more than once is acceptable or the application supplies a durable deduplication boundary.
Encryption does not provide uniqueness. An attacker does not need to decrypt captured early data to replay it. Infrastructure-level anti-replay controls can reduce exposure, but distributed deployments cannot casually promise global single execution.
Latency optimization crosses an application boundary #
In an ordinary full handshake, fresh handshake state helps bind application traffic to the connection. In 0-RTT, a client uses material derived from an earlier session and sends data immediately:
full handshake: ClientHello -> ServerHello -> request
0-RTT resume: ClientHello + request ----------->The second path saves a round trip. It also means the server may receive the same valid early request more than once. RFC 9001 explicitly identifies replay vulnerability for QUIC 0-RTT, while RFC 8470 defines how HTTP intermediaries and origins communicate early-data risk.
“POST” is not the whole policy #
HTTP method semantics are useful but insufficient. A GET that triggers an email or increments a paid counter is unsafe despite its method. A POST with a strong operation key and transactional deduplication may be replay-safe.
Classify operations by effect:
| Operation | Early-data default | Reason |
|---|---|---|
| static asset read | allow | no business mutation |
| cacheable catalogue read | allow | repeat has equivalent effect |
| account balance read | usually deny | stale or sensitive context may matter |
| payment creation | deny | duplicate financial effect |
| idempotent upsert with durable key | conditional | depends on deduplication scope |
| login or token exchange | deny | replay changes security state |
Do not infer replay safety from a framework annotation alone. Trace the operation through queues, email, billing, inventory, and third-party calls.
Use 425 as a protocol boundary #
RFC 8470 defines 425 Too Early. An origin can reject a request received in early data when it is unwilling to risk replay. A compliant client can retry after the handshake completes.
At the edge, preserve whether early data was used and enforce an allowlist. A conceptual policy is:
if request.is_early_data:
if route not in replay_safe_routes:
return 425
if request carries authorization with unsafe semantics:
return 425
forward request with trusted early-data contextNever trust a public client header that merely claims the request was or was not early data. The trusted TLS terminator must set or sanitize this context before forwarding.
Idempotency needs durable scope #
For an operation intentionally allowed to retry, store the operation key and outcome atomically with the mutation:
BEGIN;
INSERT INTO operation_result (tenant_id, operation_id, state)
VALUES ($1, $2, 'started')
ON CONFLICT (tenant_id, operation_id) DO NOTHING;
-- Proceed only when this transaction inserted the key.
-- Store the final result in the same transaction as the business mutation.
COMMIT;The key must be scoped to the authenticated tenant or principal, validated for entropy and length, retained for at least the replay/retry window, and bound to a fingerprint of the intended operation. Reusing one key with a different payload must fail.
An in-memory cache on one instance is not durable deduplication. A request replayed into another region or after a restart bypasses it.
Anti-replay has topology costs #
A single TLS terminator can track accepted tickets more easily than a global anycast fleet. Sharing replay state across regions adds coordination and latency—the very costs 0-RTT tries to remove. Keeping state local leaves cross-region replay windows.
This is why infrastructure anti-replay and business idempotency should be treated as layers:
- edge policy reduces which routes can receive early data;
- TLS ticket policy limits age and scope;
- application idempotency prevents duplicate business effects;
- audit signals detect replays and policy violations.
None should be used to claim exactly-once delivery.
Roll out from evidence #
Before enabling early data, measure how much traffic actually resumes sessions, the round-trip time saved by region, and whether that saving changes a customer SLO. Then canary only replay-safe routes.
Observe:
- early-data requests accepted and rejected by route;
425responses and successful post-handshake retries;- duplicate operation-key conflicts;
- ticket age and resumption rate;
- region changes between original and resumed connections;
- mutations that reached an early-data path unexpectedly.
Test capture-and-replay explicitly in a non-production environment. Verify that unsafe routes return 425, safe reads remain correct, and idempotent writes return the original outcome without repeating side effects.
Common mistakes #
- Assuming encrypted means non-replayable.
- Enabling 0-RTT for an entire hostname.
- Allowing early authorization or session-establishment requests.
- Treating all
GETroutes as side-effect free. - Deduplicating at the HTTP layer while a downstream consumer repeats the effect.
- Retrying a
425again as early data. - Counting median handshake improvement while ignoring low resumption rates.
The CTO decision #
0-RTT is valuable when a round trip materially affects the product and the eligible operation set is narrow and provably replay-safe. Make early-data eligibility an explicit route policy, reject unsafe work with 425, and keep durable operation identity at every side-effect boundary.
The optimization is not “turn on QUIC.” It is buying latency with a carefully bounded replay surface.