< HTTP/1.1 425 Too Early425 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.
- 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
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:
- A TLS-terminating proxy (CDN, load balancer, nginx) that accepts early data adds
Early-Data: 1to the request it forwards. - The origin, which knows whether the operation is safe to repeat, either handles the request, or answers
425 Too Early. - 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-Dataheader support upstream) should not enablessl_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_dataare 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.
Related
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
Related
TLS Handshake
The TLS handshake negotiates keys and authenticates the server before HTTP data flows. See the TLS 1.3 round trip, ALPN, session resumption and 0-RTT risks.
HTTP/3 and QUIC Explained: Setup, Fallback, 0-RTT
How HTTP/3 and QUIC work: UDP/443, connection IDs, 0-RTT replay risk, Alt-Svc and HTTPS DNS record discovery, nginx/Caddy/Cloudflare setup, and curl checks.
421 Misdirected Request: HTTP/2 Connection Coalescing
421 Misdirected Request means the server cannot answer for the host on this connection. Fix SNI and certificate mismatches caused by HTTP/2 connection reuse.
497 HTTP Request Sent to HTTPS Port (nginx)
nginx 497 means plain HTTP hit an HTTPS port; clients see "400 The plain HTTP request was sent to HTTPS port". Fix with error_page 497. Also 494, 495, 496.