Views: 28

425 Too Early: Why 0-RTT Requests Get Rejected

425 Too Early means a server refused a TLS 1.3 early-data request over replay risk. See why it fires and how to retry safely. Free instant check, no sign-up.

Check your domain for this issue now

Free, no sign-up. Runs the exact check this guide describes and shows what to fix.

Problem

You get 425 Too Early, and unlike most 4xx codes it isn’t complaining about your request. Your method is fine, your path exists, your auth is valid. The server is refusing on grounds of timing: your request showed up before it was willing to trust the connection.

This is TLS 1.3’s fault, in a good way. TLS 1.3 introduced early data — also called 0-RTT — a performance trick where a client reconnecting to a server it has seen before can send its first HTTP request immediately, packed into the opening handshake message, instead of waiting a full round trip for the handshake to finish. It’s a real latency win. It also has a hole the TLS spec is blunt about: RFC 8446 §8 states that early data carries no protection against replay at the TLS layer. An on-path attacker can record those early bytes and replay them, and nothing in the crypto distinguishes the replay from the original.

RFC 8470 is the HTTP layer’s answer. It gives a server a way to say “not like this”: the 425 (Too Early) status code means the request arrived in early data and the server won’t risk processing something replayable. Retry it after the handshake, on a connection it can trust.

Symptoms

  • The status line reads 425 Too Early, typically on a fresh connection to a host that supports TLS 1.3 with 0-RTT enabled — often a CDN or reverse proxy.
  • Browsers essentially never surface it to the user, because they retry automatically. You see it in custom clients, SDKs, scripts, and load-test tools that opted into early data.
  • It tends to hit writes (POST, PATCH, DELETE) while safe reads (GET) sail through — because non-idempotent requests are exactly the ones dangerous to replay.
  • Requests forwarded from a CDN carry an Early-Data: 1 header when they reach the origin, which is the fingerprint of an early-data request in flight.

Top 3 Causes

  1. A client opted into 0-RTT and didn’t implement the mandatory retry - The core bug. RFC 8470 §5.2 requires a client that sends early data to automatically retry on a 425 once the handshake completes. Browsers do this; a hand-rolled HTTP client, an SDK with 0-RTT flipped on, or a script often does not. So the 425 that should have been an invisible, self-healing retry becomes a hard failure the caller sees.
  2. A server refuses replayable requests in early data, by design - Not a malfunction — a policy. An origin or framework that receives a request marked Early-Data: 1 and judges it unsafe to replay (a payment, an order, any state change) answers 425 to force the client onto a confirmed connection. This is the mechanism working as intended; the request just needs to be re-sent after the handshake.
  3. A misconfigured edge sends everything as early data - The blunt-instrument case. 0-RTT is enabled at the CDN or proxy without distinguishing safe from unsafe methods, so even idempotent-only APIs see 425 churn, or the edge and origin disagree about who defers the request. The result is intermittent 425s that correlate with reconnects, not with anything about the request content.

Diagnose with DechoNet

  • HTTP Check shows the exact status line and response headers, so you can confirm the failure is 425 Too Early and not another 4xx wearing a similar number. If the same URL returns 200 on a second, warmed-up request, you’ve confirmed it’s an early-data timing issue, not a problem with the request itself.
  • SSL Check confirms the host negotiates TLS 1.3, the precondition for early data to exist at all. If the endpoint is TLS 1.3 and fronted by a CDN, 0-RTT is the plausible source of the 425 — and you know to look at the client’s retry behavior rather than the payload.

Resolution Checklist

  • Confirm it’s timing, not content: retry the exact same request on an already-established connection. If it now returns 200, the request is fine and 425 was purely an early-data rejection.
  • Check whether the failing client enabled 0-RTT. If you didn’t ask for early data, you shouldn’t be getting 425 — trace which layer (client library, proxy, CDN) turned it on.
  • If you own the client, implement the retry RFC 8470 requires: on a 425, resend the request without early data after the handshake completes. Most HTTP libraries expose a way to disable 0-RTT per request or retry on this status.
  • For anything non-idempotent — payments, orders, writes — do not send it as early data at all. Restrict 0-RTT to safe, idempotent GETs, and let writes wait for the full handshake.
  • If a CDN sits in front, check how it handles early data: whether it forwards Early-Data: 1 to your origin, and whether your origin is configured to defer or reject on it. Align the two so one of them owns the decision.

When to Escalate

  • If 425s appear for GETs that are genuinely safe to replay, the rejection is over-cautious. That’s usually an origin refusing all early data instead of just unsafe methods — a config to relax on the server side, not a client fix.
  • If a browser is somehow showing 425 to a real user, that’s a client bug worth reporting: the automatic retry that RFC 8470 mandates isn’t firing. Capture the request and the TLS version and hand it to whoever owns that client.
  • If you can’t tell whether the edge or the origin is meant to defer early data — and you’re getting intermittent 425s under load — treat it as an architecture question, not a bug hunt. Decide explicitly which hop confirms the handshake before processing state-changing requests, and configure only that one to reject.

Related Tools

Related Guides

Share this guide

[Ad] Guide Detail Inline
← Back to All Guides