412 Precondition Failed 원인과 해결
412 Precondition Failed는 If-Match ETag가 서버의 현재 버전과 더는 일치하지 않는 것입니다. 오래된 쓰기와 CDN ETag 함정을 짚어냅니다. 무료 즉시 진단으로 바로 확인.
내 도메인에 이 문제가 있는지 지금 확인
무료, 가입 불필요. 이 가이드가 다루는 항목을 바로 검사하고 조치 방법을 알려드립니다.
Problem
조건부 PUT·PATCH·DELETE가 412 Precondition Failed로 돌아옵니다. 요청은 유효했고, 인증도 통과했고, 라우트도 됩니다. 그런데 당신이 조건 — If-Match ETag나 If-Unmodified-Since 날짜 — 을 붙였고, 서버가 그 조건을 리소스의 현재 상태에 대해 검사했는데 거짓이 나왔습니다. 당신이 마지막으로 읽은 이후로 누군가 또는 무언가가 리소스를 바꾼 겁니다.
Symptoms
- 저장이 “다른 사용자가 레코드를 수정했습니다” 또는 “버전 충돌 — 새로고침하세요”로 실패합니다.
If-Match: "..."가 붙은PUT은 412인데, 조건 없는 맨PUT은 성공하고 덮어씁니다.- 오리진에 직접 보내면 사전조건이 통과하는데, CDN이나 리버스 프록시를 거치는 순간 실패합니다.
- 생성 전용
PUT에If-None-Match: *를 걸었더니 리소스가 이미 존재해서 412가 납니다.
What 412 Actually Means
RFC 9110(§15.5.13)은 412를 “요청 헤더 필드에 주어진 하나 이상의 조건이 서버에서 테스트됐을 때 거짓으로 평가된” 경우로 정의합니다. 핵심 단어는 조건입니다: 412는 절대 우연히 일어나지 않습니다. 요청이 사전조건 헤더를 실었을 때만 나타나고, 서버는 그 사전조건이 성립하지 않았다고 알려주는 겁니다.
이건 낙관적 동시성 제어(optimistic concurrency control)의 기계장치이고, 딱 하나의 문제를 풀려고 존재합니다: 손실된 업데이트(lost update). 리소스를 GET하면 서버가 ETag: "v1"을 돌려줍니다. 당신이 편집합니다. If-Match: "v1"을 붙여 PUT합니다 — “내가 읽은 그 버전 그대로일 때만 적용하라”는 뜻이죠. 그 틈에 다른 클라이언트가 "v2"를 저장했다면 서버의 ETag는 이제 "v2"이고, 당신의 사전조건은 실패하고, 412를 받습니다 — 남의 작업을 조용히 짓밟는 대신에. 그 412는 시스템이 제대로 도는 겁니다. 당신이 눈치도 못 챌 경쟁 상태를 잡아낸 거죠.
명세는 평가 순서도 못 박습니다(§13.2.2): If-Match와 If-Unmodified-Since가 If-None-Match와 If-Modified-Since보다 우선합니다. 요청이 둘 이상의 조건을 실을 때 이 순서가 중요하고, 오래된 If-Match가 나머지 전부를 단락시키는 이유입니다. 이 설계 전체가 하나의 약속입니다: 당신이 겨냥한 버전이 여전히 그 자리에 있지 않는 한, 어떤 안전하지 않은 쓰기도 착지하지 않는다.
Top 3 Causes
-
진짜 경쟁에서 온 오래된 ETag - 교과서적 경우이고, 대개 올바른 동작입니다. 버전
"v1"을 읽었는데 편집하는 동안 다른 사람이"v2"를 썼고, 당신의If-Match: "v1"이 더는 일치하지 않습니다. 서버는 더 새 데이터를 덮어쓰길 거부하고 412를 반환합니다. 여기 버그는 없습니다 — 해결은 다시GET하고, 현재 ETag를 취하고, 새 상태 위에 변경을 병합하고, 재제출. 이 412가 짜증난다면, 그게 막은 걸 기억하세요: 당신의 편집이 남의 작업을 조용히 지우는 것. -
ETag를 다시 쓰는 프록시 - 분통 터지는 경우. Apache의
mod_deflate는 응답을 압축할 때 ETag에-gzip을 붙여, 클라이언트는"abc-gzip"을 캐시하는데 오리진의 정본 ETag는"abc"입니다. 당신의If-Match는 망가진 값을 보내고, 오리진은 진짜 값과 비교하니, 동시 활동이 전혀 없어도 조건부 쓰기마다 412입니다. 일부 CDN은 ETag를 재생성하거나 통째로 벗깁니다. 신호는 오리진에는 사전조건이 되는데 엣지를 통하면 깨진다는 것. -
날짜 기반 사전조건과 시계 오차 -
If-Unmodified-Since는 초 단위 정밀도로 타임스탬프를 비교하는데, 거칠고 약합니다. 리소스가 당신이 읽은 바로 그 초에 수정됐거나, 서버의Last-Modified시계가 클라이언트와 어긋나면, 의미 있는 변화가 없어도 비교가 실패할 수 있습니다. ETag는 강한 식별자이고, HTTP 날짜는 손실 있는 근사치입니다. 가능하면If-Unmodified-Since에 날짜를 거는 대신If-Match에 ETag를 거세요.
Diagnose with DechoNet
- HTTP 진단으로 그 엔드포인트가 깨끗한
GET에 실제로 반환하는ETag와Last-Modified헤더를 보세요. 412는 당신이 보내는 검증자(validator)와 리소스가 현재 가진 검증자 사이의 불일치이므로, 첫 수는 현재 것을 읽는 겁니다. 진단이 보고하는 ETag가 당신 클라이언트가 캐시한 것과 다르다면, 그게 답입니다 — 당신은 사라진 버전에 조건을 걸고 있습니다. 진단에 ETag가 아예 없다면, 그 엔드포인트는 당신 클라이언트가 가정하는 방식의 조건부 요청을 지원하지 않을 수 있습니다.
Resolution Checklist
-
If-Match에 보내는 ETag를 리소스가 지금 반환하는 ETag와 비교하세요. 다르다면 리소스가 움직인 것 — 쓰기 전에 다시 읽으세요. - 진짜 오래된 쓰기 경쟁이면, 리소스를 다시
GET하고, 새 ETag를 취하고, 현재 상태 위에 변경을 다시 얹고, 새If-Match로 재제출하세요. 같은 오래된 조건을 절대 반복하지 마세요 — 영원히 412입니다. - Apache + 압축 뒤에 있다면
-gzipETag 접미사를 확인하세요. 접미사를 끄거나(최신 Apache의DeflateAlterETag NoChange)If-Match를 보내기 전에 클라이언트 측에서 벗기세요. - 오리진에는 사전조건이 통과하는데 CDN을 통하면 실패한다면, 엣지가 서빙하는 ETag와 오리진이 설정하는 ETag를 비교하세요. ETag를 재생성하는 프록시는 조건부 요청을 설계상 깨뜨립니다 — 오리진의 값을 건드리지 않고 통과시키도록 설정하세요.
- 날짜의
If-Unmodified-Since보다 강한 ETag의If-Match를 선호하세요. 초 단위 타임스탬프는 엔티티 태그라면 피할 가짜 412를 유발합니다. - 현재 검증자로 조건부 요청을 다시 돌려 이제 2xx가 나오는지 확인하세요.
When to Escalate
- 동시성이 전혀 없는데 클라이언트가 조건부 쓰기마다 412를 맞는다면, ETag 파이프라인이 오리진과 클라이언트 사이 어딘가에서 깨진 겁니다 — 보통 태그를 다시 쓰거나 벗기는 프록시. 인프라 수정이고, 엣지를 소유한 쪽의 몫입니다.
- 낙관적 동시성을 지원해야 할 리소스가 정당한 첫 쓰기에서 412를 반환한다면, 상위 캐시가 이전 버전의 오래된 ETag를 서빙하는지 확인하세요. 리소스 상태보다 오래 사는 캐시된 검증자는 모든 조건부 쓰기를 가짜 충돌로 만듭니다.
관련 도구
관련 가이드
가이드 공유