조회수: 102

428 Precondition Required 원인과 해결

428 Precondition Required는 서버가 If-Match ETag 없이는 PUT/PATCH를 거부하는 것입니다. ETag를 GET해 재전송하고 lost update를 막습니다. 무료 즉시 진단으로 바로 확인.

내 도메인에 이 문제가 있는지 지금 확인

무료, 가입 불필요. 이 가이드가 다루는 항목을 바로 검사하고 조치 방법을 알려드립니다.

Problem

PUT·PATCH·DELETE428 Precondition Required로 돌아옵니다. 인증도 통과했고, 라우트도 맞고, 본문도 검증됩니다 — 그런데 조건 없이 보냈다는 이유로 쓰기가 거부됩니다. 서버에는 정책이 있습니다: 리소스의 특정 버전에 고정되지 않은 변경은 받지 않는다. 그런데 당신의 요청에는 그걸 고정할 If-Match(또는 If-Unmodified-Since) 헤더가 없었습니다. 같은 리소스에 대한 GET은 잘 됩니다 — 읽기는 사전조건이 필요 없으니까.

Symptoms

  • 같은 엔드포인트에서 GET은 200인데 PATCH·PUT은 428.
  • 에러 본문에 “If-Match 헤더가 필요하다” 또는 “precondition required” 비슷한 말.
  • ETag를 담은 If-Match: "..."를 추가하는 순간 동일 요청이 되기 시작.
  • 쓰기만 실패 — 읽기, 헬스체크, 무관한 엔드포인트는 다 정상.
  • OData·SAP·엔터프라이즈 API가 엔티티 ETag를 왕복시키기 전엔 모든 업데이트를 거부.

What 428 Actually Means

RFC 6585(§3)은 428을 오리진 서버가 요청이 조건부이길 요구하는 것으로 정의합니다: “전형적 용도는 ‘lost update’ 문제를 피하는 것 — 클라이언트가 리소스 상태를 GET해 수정하고 PUT으로 되돌려 보내는데, 그사이 제3자가 서버의 상태를 바꿔 충돌이 일어나는 상황.” 응답은 성공적으로 재제출하는 법을 설명해야 합니다(SHOULD) — 대개 원하는 헤더를 지목해서.

이건 낙관적 동시성 제어를 의무화한 것입니다. lost update가 존재 이유의 전부입니다: 두 클라이언트가 버전 "v1"을 읽고, 둘 다 편집하고, 둘 다 되돌려 씁니다. 사전조건이 없으면 두 번째 쓰기가 첫 번째를 조용히 덮어쓰고, 첫 클라이언트의 변경은 에러 하나 없이 사라집니다. 428을 반환하는 서버는 그 게임을 거부하는 겁니다. 모든 쓰기가 어느 버전을 대체할 셈인지 선언하라 — If-Match: "v1" — 요구해서, 당신이 읽은 이후 리소스가 넘어갔으면 쓰기를 거절할 수 있게 합니다.

428은 412 Precondition Failed의 거울상입니다. 412는 사전조건이 있었고 거짓으로 평가될 때. 428은 사전조건이 요구됐는데 없을 때. 그리고 412와 달리 428은 nginx나 Apache가 기본으로 내는 일이 거의 없습니다 — 애플리케이션 수준의 결정입니다. 어떤 프레임워크·API 게이트웨이·핸들러가 조건부 쓰기를 요구하도록 명시적으로 작성된 겁니다. 최고의 실전 사례는 엔터프라이즈 데이터 API입니다: SAP S/4HANA의 OData 서비스는 엔티티 ETag를 If-Match로 싣지 않은 모든 PATCH에 428을 반환합니다.

Top 3 Causes

  1. API가 설계상 낙관적 동시성을 강제 - 버그가 아니라 의도된 경로. SAP OData·Salesforce 같은 플랫폼은 모든 변경을 조건부로 요구합니다. 리소스를 GETETag를 읽고 쓰기의 If-Match로 되돌려 보내야 합니다. 428은 서버가 자기 계약을 가르치는 것 — 고정 안 된 쓰기 불가. 검증자를 왕복시키면 쓰기가 통과합니다.

  2. 클라이언트가 헤더를 안 보냄 - If-Match를 넣으려 했는데 도착하지 않은 경우. 커스텀 헤더를 전달하지 않는 클라이언트 라이브러리, 지나가며 If-Match를 벗기는 리버스 프록시, 헤더가 빠진 복붙 요청 모두 서버 문제처럼 보이지만 실은 헤더 누락인 428을 냅니다. 단서: 헤더를 손으로 넣은 REST 클라이언트에선 요청이 됩니다.

  3. 보낼 검증자가 없음 - If-Match를 붙이려는데 GET이 쓸 ETag를 준 적이 없는 경우. 리소스의 GET 응답에 ETagLast-Modified도 없으면 — 엔드포인트가 안 내거나 CDN이 벗겼거나 — 조건 걸 게 없어 모든 쓰기가 428에서 막힙니다. 이건 서버 측 계약 파손입니다: 사전조건을 요구하면서 그걸 충족할 검증자를 주지 않는 것.

Diagnose with DechoNet

  • HTTP Check로 엔드포인트가 GET에 실제로 검증자를 반환하는지 확인하세요. 그것 없이는 428을 충족할 수 없습니다 — If-Match엔 ETag가, If-Unmodified-SinceLast-Modified가 필요합니다. 체크가 GET 응답에 실제 ETag가 실렸다고 보여주면, 그걸 캡처해 쓰기에 되돌려 실으면 그게 해결의 전부입니다. GETETagLast-Modified도 전혀 반환하지 않으면 동시성 계약이 파손된 겁니다 — 서버가 만들 수단도 주지 않으면서 사전조건을 요구하는 것이고, 이건 클라이언트가 아니라 서버 측 버그입니다.

Resolution Checklist

  • 리소스를 GET하고 응답 헤더에서 ETag를 캡처(그것만 준다면 Last-Modified).
  • 쓰기를 If-Match: "<etag>"(또는 If-Unmodified-Since: <date>)로 재전송하고 이제 2xx가 나오는지 확인.
  • GET이 검증자를 안 주면 CDN·프록시가 ETag를 벗기는지 확인 — 없이는 428을 충족할 수 없습니다. 오리진이 정말 안 내면 서버 측에서 고쳐야 합니다.
  • If-Match: *는 정말 “어떤 버전이든 존재하면 쓴다”를 뜻할 때만 쓰세요 — 428은 사라지지만 코드가 집행하던 lost-update 방어를 끕니다.
  • If-Match를 더했더니 428이 412로 바뀌면 ETag가 낡은 겁니다. 다시 GET해 새 검증자를 취하고 재시도 — 옛것을 반복하지 마세요.
  • 실제 조건에서 검증: 현재 ETag로는 쓰기가 성공하고 낡은 ETag로는 깔끔히 실패(412)해야 합니다. 둘 다 성공하면 사전조건이 실제로 집행되지 않는 겁니다.

When to Escalate

  • GETETagLast-Modified도 안 주는데 API가 여전히 쓰기에 428을 요구하면 계약 버그입니다: 서버가 논리적으로 충족될 수 없습니다. 고치려면 API 팀이 읽기에 검증자를 내야 하고, 클라이언트가 우회할 방법은 목적을 무너뜨리는 If-Match: *뿐입니다.
  • If-Match가 오리진 직결에선 되는데 엣지를 통하면 428이 지속되면, 프록시가 클라이언트와 오리진 사이에서 헤더를 벗기는 겁니다. 인프라 문제이고 게이트웨이·CDN을 소유한 사람의 몫입니다.

관련 도구

관련 가이드

가이드 공유

[Ad] Guide Detail Inline
← 전체 가이드 보기