How HTTP Works
4xx · Client error
< HTTP/1.1 425 Too Early

425 Too Early: TLS 1.3 0-RTT Replay Protection

425 Too Early tells a client not to send a request in TLS early data because it could be replayed. See the Early-Data header and nginx config for 0-RTT.

Reviewed 2 min readadvanced4 sourcesTry itMarkdown
Cacheable
Only with explicit freshness
Retry?
Yes: resend once the handshake completes
Usually sent by
Origin or TLS terminator (0-RTT early data)
Spec
RFC 8470
On this page

TL;DR: 425 Too Early is the server saying “this arrived in TLS 1.3 early data and I will not process it, because an attacker could replay it. Send it again after the handshake.” It exists so 0-RTT can be used for resumption speed without replaying unsafe requests (RFC 8470).

Why it exists

TLS 1.3 session resumption can carry application data in the very first flight (0-RTT). That saves a round trip, but early data is replayable: an attacker who records the packet can send it again to the server, which may process it twice. RFC 8446 §8 leaves protection to the application. RFC 8470 defines the HTTP half:

  1. A TLS-terminating proxy (CDN, load balancer, nginx) that accepts early data adds Early-Data: 1 to the request it forwards.
  2. The origin, which knows whether the operation is safe to repeat, either handles the request, or answers 425 Too Early.
  3. The client retries it once the handshake has completed, which can no longer be replayed.
POST /orders HTTP/1.1
Host: shop.example.com
Early-Data: 1
Content-Type: application/json

HTTP/1.1 425 Too Early

Configure it

nginx must opt in to early data and tell the upstream:

server {
    listen 443 ssl;
    ssl_protocols TLSv1.3;
    ssl_early_data on;

    location / {
        proxy_pass http://app_backend;
        proxy_set_header Early-Data $ssl_early_data;
    }
}

$ssl_early_data is 1 when the request was received in early data and the handshake had not completed, and empty otherwise. The origin then rejects unsafe methods:

app.use((req, res, next) => {
  const early = req.headers['early-data'] === '1'
  if (early && !['GET', 'HEAD', 'OPTIONS'].includes(req.method)) {
    return res.status(425).end()
  }
  next()
})

A GET that has side effects (logging in via a link, burning a one-time code) is not safe either, and RFC 8470 says the application decides, not the method name.

Notes

  • A TLS-terminating proxy that cannot tell the origin about early data (no Early-Data header support upstream) should not enable ssl_early_data, because the origin would have no way to refuse unsafe requests.
  • CDNs that offer 0-RTT as a switch (Cloudflare calls it 0-RTT Connection Resumption) forward the signal to the origin; check the vendor docs for the exact header they set.
  • 425 is not cacheable by default and is not an indication of a broken server. Elevated 425s after enabling ssl_early_data are expected for returning clients.
  • Without ssl_early_data on, nginx never accepts early data, and you will never see this code from it.

Try it with curl

425 Too Early is sent by an origin that sees Early-Data: 1 on a request it will not process. That header is normally added by a TLS-terminating proxy, not by clients, so you can simulate it by sending it directly to an origin that implements RFC 8470. curl 8.7 does not send real TLS 1.3 early data.

curl -i -X POST https://origin.example.com/orders -H 'Early-Data: 1' -d '{"sku":"A1"}'

If the origin supports the check, the response is HTTP/1.1 425 Too Early. Without that support, the header is ignored.

Frequently asked questions

What does 425 Too Early mean?

The server refused to process the request because it arrived in TLS 1.3 early data (0-RTT) and could be replayed by an attacker. The client should retry the same request after the handshake completes, when replay is no longer possible.

What is the Early-Data header?

A request header with the value 1 that a TLS-terminating proxy adds when forwarding a request that arrived in early data. The origin uses it to decide whether the request is safe to process or should be answered with 425.

Which requests are safe in 0-RTT?

Those without side effects: typically GET without sensitive consequences. Early data has no replay protection at the TLS layer, so the application must refuse anything that changes state, such as POST, payments or one-time tokens.

Do I need to handle 425 in my client?

Only if you send early data deliberately. Browsers and HTTP libraries that use 0-RTT retry automatically after 425. If you write a custom client that enables TLS early data, you must resend the request after the handshake.

Sources

  1. MDN Web Docs: 425 Too Earlydeveloper.mozilla.org
  2. RFC 8470: Using Early Data in HTTPrfc-editor.org
  3. RFC 8446 Section 8: 0-RTT and Anti-Replayrfc-editor.org
  4. nginx: ssl_early_datanginx.org

Keep going

Browse /search