HTTP 412 Precondition Failed

Precondition Failed

HTTP 412 is Precondition Failed: a condition in the request’s headers evaluated to false on the server, so the method wasn’t applied (RFC 9110 section 15.5.13). It’s how a client says “only do this if the resource is still the way I last saw it”, and how the server says “it isn’t”.

Optimistic locking

GET /docs/42 HTTP/1.1

HTTP/1.1 200 OK
ETag: "v7"

PUT /docs/42 HTTP/1.1
If-Match: "v7"
Content-Type: application/json

{"title": "Q3 plan"}

HTTP/1.1 412 Precondition Failed

Someone saved version 8 in between, so the server refuses instead of silently overwriting it: the lost-update problem. The client fetches the document again, reapplies its change and retries with the new ETag.

The headers that produce it

If-Match: "v7"                  412 unless the current ETag is "v7"
If-Match: *                     412 if the resource doesn't exist
If-None-Match: *   (on a PUT)   412 if it already exists: create it, never overwrite
If-Unmodified-Since: <date>     412 if it changed since; ignored when If-Match is present

RFC 9110 section 13.2.2 fixes the order: If-Match first, If-Unmodified-Since only without it, then If-None-Match, which yields 304 on a GET or HEAD and 412 on any other method. If-Match uses strong comparison, so a weak ETag like W/"v7" never matches it.

412, 428, 409 or 304

a conditional GET found your copy still fresh         304 Not Modified
the condition you sent was false                      412 Precondition Failed
you sent no condition, and the server requires one    428 Precondition Required
the request clashes with the state some other way     409 Conflict

Not everyone uses 412 for stale versions: Kubernetes answers an update with an outdated resourceVersion with 409 Conflict.

Who sends it

  • Amazon S3 supports conditional writes: If-None-Match: * on a PUT fails with 412 when the key exists, and If-Match fails it when the object’s ETag has changed.
  • Google Cloud Storage fails x-goog-if-generation-match and its other preconditions with 412.
  • Azure Blob Storage answers a failed If-Match with 412.
  • Go’s http.ServeContent evaluates If-Match and If-Unmodified-Since and answers 412 itself.

Gotchas

  • A retried write. RFC 9110 lets the server answer success instead of 412 when it can tell the same change already succeeded, such as a PUT whose response was lost.
  • If-Range never produces 412. A failed If-Range makes the server ignore Range and send the whole representation as a 200.
  • Make check and write atomic, for example UPDATE ... WHERE version = 7, or two writers can both pass the check.
  • Caching. 412 isn’t heuristically cacheable.
# Create-only upload: fails with 412 if the object is already there
curl -X PUT -H 'If-None-Match: *' --data-binary @q3.pdf https://storage.example.com/reports/q3.pdf

Search 412 428 in the tool below to compare the two.

Try it in the HTTP Status Codes

412 Precondition Failed

  1. 412Precondition Failed4xx Client error

    One of the conditions on the request — If-Match, If-Unmodified-Since — did not hold, so nothing was changed.

    When to use it

    Conditional writes that must not overwrite somebody else’s newer change.

    Common mistakes

    Confusing it with 409, which is about a clash with the resource itself, and with 304, which answers a conditional read.

    Not cacheable by defaultRFC 9110

Every code, by class

Codes marked unofficial come from one server’s own convention — nginx, Cloudflare, cPanel, Microsoft — and are not in the IANA registry.

Runs in your browser. Nothing you enter is sent anywhere.