HTTP status code · Client errors (4xx)
425 Too Early
425 Too Early means the server will not process a request that arrived in TLS 1.3 early data (0-RTT), because an attacker could replay it. It happens when a resumed connection sends a non-idempotent request, such as a POST that makes a payment, before the handshake is done.
| Class | 4xx, Client errors |
|---|---|
| Defined in | RFC 8470 §5.2 |
| Cacheable by default | Only with explicit Cache-Control or Expires; RFC 8470 says it is not cacheable by default |
| Safe to retry | Yes, automatically, once the TLS handshake completes and never again as early data |
| Relevant headers |
|
What 425 means
TLS 1.3 lets a returning client send its first request together with the handshake, saving a round trip. That early data has no replay protection: someone who captured it can send it again. RFC 8470 defines 425 so a server, or the origin behind a CDN, can say "not in early data" for requests where a replay would do harm.
A proxy that accepts 0-RTT adds Early-Data: 1 when it forwards such a request, and must pass a 425 back to the client unchanged. The client is expected to retry automatically after the handshake completes, so users normally never see a 425; browsers only send idempotent requests like GET in early data in the first place.
Common causes
If you run the server
- 0-RTT is enabled at the edge (nginx ssl_early_data on, or a CDN setting) and the origin rejects state-changing requests that carry Early-Data: 1.
- An HTTP client library sends POST in early data, which most servers that honor RFC 8470 refuse.
How to fix it
If you run the server
- Let the client retry: a compliant client resends after the handshake, and the second attempt succeeds.
- Only reject when there is real replay risk: safe methods (GET, HEAD) with no side effects can be served from early data.
- If a custom client keeps failing, turn off 0-RTT in it; the cost is one extra round trip on resumed connections.
How to send 425
app.post('/payments', (req, res) => {
if (req.get('Early-Data') === '1') {
return res.status(425).json({ error: 'Retry after the TLS handshake completes' });
}
// ... charge the card
});// app/payments/route.ts
export async function POST(request: Request) {
if (request.headers.get('early-data') === '1') {
return Response.json({ error: 'Retry after the TLS handshake completes' }, { status: 425 });
}
// ... charge the card
}mux.HandleFunc("POST /payments", func(w http.ResponseWriter, r *http.Request) {
if r.Header.Get("Early-Data") == "1" {
http.Error(w, "retry after the TLS handshake completes", http.StatusTooEarly) // 425
return
}
// ... charge the card
})from fastapi import FastAPI, HTTPException, Request
app = FastAPI()
@app.post("/payments")
def create_payment(request: Request):
if request.headers.get("early-data") == "1":
raise HTTPException(status_code=425, detail="Retry after the TLS handshake completes")
# ... charge the card# Accept 0-RTT at the edge, tell the app which requests came in early data
ssl_early_data on;
location / {
proxy_set_header Early-Data $ssl_early_data;
proxy_pass http://app;
}
# The app answers 425 to unsafe requests with Early-Data: 1Commonly confused with
- 425 vs 429
- 429 says you sent too many requests and must wait; 425 says this one request came too early in the connection and can be retried a moment later.
- 425 vs 426
- 426 asks the client to switch protocols; 425 keeps the protocol and only asks it to wait for the TLS handshake.
Frequently asked questions
- What is TLS 1.3 early data (0-RTT)?
- A feature that lets a client resuming an earlier session send application data in its very first flight, before the handshake finishes. It saves one round trip but offers no protection against replay, which is why servers refuse risky requests in it with 425.
- Will a browser show me a 425 error?
- Almost never. Browsers only put safe requests in early data, and when a server does answer 425 the browser retries after the handshake without showing anything.
- Which requests should get 425?
- Requests that change state and would be harmful if processed twice, such as payments, orders or password changes, when they arrive with Early-Data: 1 or directly in early data. RFC 8470 says not to send 425 otherwise, because the client may have no way to retry.
Last reviewed by Arielton Oberek.