HTTP 0-RTT + 425 Too Early β€” Replay-Safe API Gateway Playbook

2026-03-26 Β· software

HTTP 0-RTT + 425 Too Early β€” Replay-Safe API Gateway Playbook

Date: 2026-03-26
Category: knowledge
Audience: API platform / edge / security engineers running low-latency HTTPS services

1) Why this matters

TLS 1.3 and QUIC 0-RTT can reduce connect latency by letting clients send application data immediately on resumed sessions.

That speed comes with a trade-off: 0-RTT data can be replayed. If replayed requests hit state-changing endpoints, you can get duplicate side effects (duplicate orders, duplicate transfers, repeated writes).

For high-impact APIs, the right operating model is:


2) Protocol truth table (what standards actually imply)

TLS / QUIC layer

HTTP layer (RFC 8470)

Practical implication

GET is not automatically safe in your system, and POST is not automatically unsafe if strictly idempotent.

The real classifier is: β€œCan replay of this exact request create harmful additional effects?”


3) Endpoint policy model (ship this first)

Define replay policy per endpoint:

  1. ALLOW_0RTT
    Safe reads / cache lookups / health checks.

  2. REQUIRE_1RTT
    State changes, order placement, payment, inventory reservations, workflow transitions.

  3. ALLOW_0RTT_WITH_IDEMPOTENCY
    Mutations only if strong idempotency key + dedup window is enforced.

Example policy table:


4) Gateway behavior blueprint

At edge/proxy:

  1. Detect early data signal (Early-Data: 1 from CDN/proxy, or local TLS early-data state).
  2. Resolve endpoint replay policy.
  3. If request is early and policy forbids it:
    • return 425 Too Early immediately.
  4. Otherwise, forward with replay metadata for observability.

Minimal NGINX-ish shape:

# TLS 1.3 early data enabled
ssl_early_data on;

# Pass signal to app
proxy_set_header Early-Data $ssl_early_data;

App-side logic (pseudo):

if header("Early-Data") == "1":
  if policy(endpoint) == REQUIRE_1RTT:
    return 425
  if policy(endpoint) == ALLOW_0RTT_WITH_IDEMPOTENCY:
    require Idempotency-Key
    if duplicate_seen(key, hash(body), window=24h):
      return cached_or_conflict_response
process_normally()

5) Idempotency design that survives replay

If you allow any mutation in 0-RTT, implement all of:

Without this, 0-RTT on mutating endpoints is usually an operational liability.


6) Observability: metrics to add on day one

Track separately for early-data traffic:

Add structured logs fields:


7) Rollout plan (low-drama)

Phase 0 β€” Inventory

Classify all public endpoints into the three policy buckets.

Phase 1 β€” Safe-only enablement

Enable 0-RTT only for ALLOW_0RTT routes. Force 425 for all risky paths.

Phase 2 β€” Hardened mutation pilots

For one low-risk mutation endpoint, add strict idempotency + replay observability, then cautiously allow 0-RTT.

Phase 3 β€” Continuous verification

Run chaos/replay simulations in staging and periodically in prod canaries.


8) Common failure modes


9) Bottom line

0-RTT is a latency feature, not a free speed boost.

If your API has side effects, pair it with an explicit replay policy (ALLOW / REQUIRE_1RTT / ALLOW_WITH_IDEMPOTENCY), enforce 425 Too Early where needed, and prove correctness with dedup telemetry.

That turns 0-RTT from a security footgun into a controlled performance optimization.


References

  1. RFC 8446 β€” The Transport Layer Security (TLS) Protocol Version 1.3
    https://www.rfc-editor.org/rfc/rfc8446
  2. RFC 8470 β€” Using Early Data in HTTP
    https://www.rfc-editor.org/rfc/rfc8470
  3. RFC 9001 β€” Using TLS to Secure QUIC
    https://www.rfc-editor.org/rfc/rfc9001
  4. NGINX docs β€” ssl_early_data and replay caveat
    https://nginx.org/en/docs/http/ngx_http_ssl_module.html
  5. Cloudflare blog β€” QUIC 0-RTT resumption, Early-Data: 1, and 425 pattern
    https://blog.cloudflare.com/even-faster-connection-establishment-with-quic-0-rtt-resumption/
  6. AWS NLB TLS listener note (0-RTT / early_data not implemented)
    https://docs.aws.amazon.com/elasticloadbalancing/latest/network/load-balancer-listeners.html