425 Too Early: Why 0-RTT Requests Get Rejected
425 Too Early는 서버가 리플레이 공격을 피하려 TLS 1.3 early-data 요청을 거부한 것입니다. 왜 뜨는지, 어떻게 안전하게 재시도할지. 무료 HTTP 진단으로 바로 확인.
내 도메인에 이 문제가 있는지 지금 확인
무료, 가입 불필요. 이 가이드가 다루는 항목을 바로 검사하고 조치 방법을 알려드립니다.
문제
425 Too Early가 떴는데, 대부분의 4xx와 달리 이건 당신의 요청을 탓하는 게 아닙니다. 메서드도 멀쩡하고, 경로도 존재하고, 인증도 유효합니다. 서버는 타이밍을 이유로 거부합니다: 요청이 연결을 신뢰할 준비가 되기 전에 도착한 것입니다.
이건 좋은 의미로 TLS 1.3 탓입니다. TLS 1.3은 early data — 0-RTT라고도 하는 — 성능 기법을 도입했습니다. 본 적 있는 서버에 재접속하는 클라이언트가 핸드셰이크가 끝나길 왕복 한 번 기다리는 대신, 첫 HTTP 요청을 여는 핸드셰이크 메시지에 실어 즉시 보내는 것입니다. 실질적인 지연 이득입니다. 그런데 TLS 명세가 대놓고 말하는 구멍이 있습니다: RFC 8446 §8은 early data가 TLS 계층에서 리플레이에 대한 보호가 없다고 명시합니다. 경로상의 공격자가 그 앞부분 바이트를 기록해 다시 보낼 수 있고, 암호학적으로 리플레이와 원본을 구분할 방법이 없습니다.
RFC 8470은 HTTP 계층의 답입니다. 서버에 “이렇게는 안 돼”라고 말할 방법을 줍니다: 425 (Too Early) 상태 코드는 요청이 early data로 도착했고 서버가 리플레이 가능한 것을 처리할 위험을 감수하지 않겠다는 뜻입니다. 신뢰할 수 있는 연결에서, 핸드셰이크 이후에 다시 보내라는 것입니다.
증상
- 상태 줄이
425 Too Early로 나오고, 대개 0-RTT가 켜진 TLS 1.3 지원 호스트 — 흔히 CDN이나 리버스 프록시 — 로의 새 연결에서 발생합니다. - 브라우저는 자동 재시도하므로 사용자에게 사실상 절대 표출하지 않습니다. early data를 켠 커스텀 클라이언트, SDK, 스크립트, 부하 테스트 도구에서 보게 됩니다.
- 안전한 읽기(
GET)는 통과하는데 쓰기(POST,PATCH,DELETE)에 주로 걸립니다 — 비멱등 요청이 바로 리플레이하면 위험한 것들이기 때문입니다. - CDN에서 전달된 요청은 오리진에 닿을 때
Early-Data: 1헤더를 달고 오는데, 이것이 진행 중인 early-data 요청의 지문입니다.
주요 원인 3가지
- 클라이언트가 0-RTT를 켜고 의무 재시도를 구현 안 함 - 핵심 버그입니다. RFC 8470 §5.2는 early data를 보낸 클라이언트가 핸드셰이크 완료 후 425에 대해 자동 재시도하도록 요구합니다. 브라우저는 합니다. 직접 만든 HTTP 클라이언트, 0-RTT가 켜진 SDK, 스크립트는 흔히 안 합니다. 그래서 보이지 않고 스스로 낫는 재시도였어야 할 425가 호출자가 보는 하드 실패가 됩니다.
- 서버가 리플레이 가능한 요청을 설계상 거부 - 오작동이 아니라 정책입니다.
Early-Data: 1로 표시된 요청을 받은 오리진·프레임워크가 리플레이하면 안전하지 않다고(결제, 주문, 상태 변경) 판단해 425를 답하고 확인된 연결로 클라이언트를 밀어냅니다. 의도대로 동작하는 메커니즘이고, 요청은 핸드셰이크 이후 재전송만 하면 됩니다. - 오설정된 엣지가 모든 걸 early data로 보냄 - 무딘 도구의 경우입니다. 안전·비안전 메서드를 구분하지 않고 CDN·프록시에서 0-RTT가 켜져, 멱등 전용 API조차 425 churn을 겪거나, 엣지와 오리진이 누가 요청을 지연할지 서로 어긋납니다. 결과는 요청 내용이 아니라 재접속과 상관되는 간헐적 425입니다.
DechoNet으로 진단하기
- HTTP 점검은 정확한 상태 줄과 응답 헤더를 보여주므로, 실패가 비슷한 숫자를 뒤집어쓴 다른 4xx가 아니라
425 Too Early임을 확인할 수 있습니다. 같은 URL이 두 번째 예열된 요청에서200을 반환하면, 요청 자체 문제가 아니라 early-data 타이밍 문제임이 확인됩니다. - SSL 점검은 호스트가 TLS 1.3을 협상하는지 — early data가 존재하려면 반드시 필요한 전제 — 확인합니다. 엔드포인트가 TLS 1.3이고 CDN 뒤에 있다면 0-RTT가 425의 유력한 출처이고, 페이로드가 아니라 클라이언트의 재시도 동작을 봐야 함을 알 수 있습니다.
해결 체크리스트
- 내용이 아니라 타이밍임을 확인: 같은 요청을 이미 수립된 연결에서 재시도하세요. 이제
200이면 요청은 멀쩡하고 425는 순수한 early-data 거부였습니다. - 실패한 클라이언트가 0-RTT를 켰는지 확인하세요. early data를 요청하지 않았다면 425를 받을 이유가 없습니다 — 어느 계층(클라이언트 라이브러리, 프록시, CDN)이 켰는지 추적하세요.
- 클라이언트를 당신이 소유한다면 RFC 8470이 요구하는 재시도를 구현하세요: 425에 대해 핸드셰이크 완료 후 early data 없이 재전송. 대부분의 HTTP 라이브러리는 요청별 0-RTT 비활성화나 이 상태에서의 재시도 수단을 제공합니다.
- 비멱등인 것 — 결제, 주문, 쓰기 — 은 아예 early data로 보내지 마세요. 0-RTT를 안전한 멱등
GET으로 제한하고, 쓰기는 전체 핸드셰이크를 기다리게 하세요. - 앞에 CDN이 있다면 early data를 어떻게 다루는지 확인하세요:
Early-Data: 1을 오리진으로 전달하는지, 오리진이 그에 대해 지연·거부하도록 설정됐는지. 둘 중 하나가 결정을 소유하도록 정렬하세요.
언제 에스컬레이션할까
- 진짜 리플레이해도 안전한
GET에 425가 뜬다면 거부가 과합니다. 대개 오리진이 비안전 메서드만이 아니라 모든 early data를 거부하는 경우입니다 — 클라이언트가 아니라 서버 쪽에서 완화할 설정입니다. - 브라우저가 어떻게든 실제 사용자에게 425를 보여준다면 보고할 만한 클라이언트 버그입니다: RFC 8470이 의무화한 자동 재시도가 안 도는 것입니다. 요청과 TLS 버전을 캡처해 그 클라이언트 소유자에게 넘기세요.
- 엣지가 지연해야 하는지 오리진이 해야 하는지 판단이 안 서고 — 부하에서 간헐적 425가 난다면 — 버그 사냥이 아니라 아키텍처 질문으로 다루세요. 상태 변경 요청을 처리하기 전에 어느 홉이 핸드셰이크를 확인하는지 명시적으로 정하고, 그 하나만 거부하도록 설정하세요.
관련 도구
관련 가이드
가이드 공유