412 Precondition Failed 4xx
A condition specified in the request's headers was not met by the current state of the resource.
What does 412 mean?
A 412 Precondition Failed response means the client included one or more conditional headers (If-Match, If-Unmodified-Since, If-None-Match, If-Modified-Since) asserting something about the current state of the resource, and that assertion turned out to be false. The request was otherwise valid — it's the precondition the client attached to it that didn't hold.
This is closely related to 304 (Not Modified), but represents the opposite outcome for a different kind of conditional request: where 304 is the "good news" response to If-None-Match/If-Modified-Since (used for caching — "you already have the latest, nothing to do"), 412 is the "stop" response to If-Match/If-Unmodified-Since (used for safe writes — "you're trying to modify this based on an outdated assumption about its state, so I won't proceed").
How a 412 behaves
- It's triggered by conditional request headers — without an
If-Match,If-Unmodified-Since, or similar header, 412 can't occur, since there's no precondition to fail - It typically doesn't include the resource's current state in the body — unlike 409, which often explains the conflict in detail, 412 is more of a blunt "your precondition didn't hold"
- It's not cacheable
- It prevents the requested operation from happening at all — unlike 409 (which means the operation conflicts with current state but the server processed the request to discover this), 412 means the server checked the precondition before attempting the operation and stopped immediately
Common causes
If you're building the API or website:
- A client sent
If-Match: <etag>with a PUT/PATCH/DELETE request, asserting "only do this if the resource's current ETag matches this value" — and the resource has since changed, so the ETags don't match - A client sent
If-Unmodified-Since: <date>asserting "only proceed if this hasn't changed since this date" — and it has changed since then
If you're calling an API:
- You fetched a resource, got its
ETag, then tried to update it withIf-Match: <that etag>— but someone else modified the resource in the meantime, so the currentETagno longer matches what you have - You're implementing optimistic concurrency control and a 412 is the expected, correct response when a conflicting update has occurred since you last fetched the resource
If you're a website visitor:
- Essentially never encountered directly — conditional requests with
If-Match/If-Unmodified-Sinceare an API/application-layer mechanism, not something that surfaces during normal browsing
How to fix it
As an API/website builder:
- If you support conditional requests with
If-Matchfor safe updates (a good practice for preventing lost-update problems), returning 412 when the precondition fails is exactly correct — this is the mechanism working as intended - Consider whether your 412 response should include the resource's current state (and current
ETag), so the client can immediately see what changed without an additional GET request
As an API consumer:
- A 412 on a conditional update means the resource changed since you last fetched it — re-fetch the current version, reconcile your intended changes with whatever changed, and retry the update with the new
ETag/timestamp - This is the expected behavior for optimistic concurrency control — handle it as "someone else updated this, let's reconcile" rather than treating it as an unexpected error
As a website visitor:
- Not applicable directly — if an application surfaces this as a user-facing message, it likely means "this was changed by someone else since you started editing — please refresh and try again"
412 vs 409 vs 304
| Code | Trigger | Meaning |
|---|---|---|
| 304 Not Modified | If-None-Match/If-Modified-Since on a GET |
"Your cached copy is still valid — here's nothing new" (used for caching) |
| 412 Precondition Failed | If-Match/If-Unmodified-Since on a write (PUT/PATCH/DELETE) |
"Your assumption about the resource's current state doesn't hold — I won't proceed" (used for safe concurrent writes) |
| 409 Conflict | No specific conditional header required | "The request conflicts with current state in some way" — a broader category that can overlap with 412's use case, but doesn't require conditional headers |
Real-world examples
APIs implementing robust optimistic concurrency control commonly support If-Match with ETag values on update/delete endpoints — a client fetches a resource (receiving its current ETag), then includes If-Match: <etag> on a subsequent PUT, and receives 412 if another client has modified the resource in the meantime, preventing the "lost update" problem where one client's changes silently overwrite another's.
SEO implications
412 is exclusively relevant to conditional API write operations and has no bearing on page-level SEO.
FAQ
What's the difference between 412 and 409?
412 specifically results from a conditional request header (If-Match, If-Unmodified-Since) not matching the resource's current state — it's a precondition check that happens before any operation is attempted. 409 is a broader "this conflicts with current state" response that doesn't require any specific conditional header to trigger.
Is 412 related to 304?
Yes, conceptually — both are outcomes of conditional request headers comparing client-supplied values against a resource's current state. 304 is the "match found, nothing to do" outcome for read-oriented conditional headers (If-None-Match). 412 is the "no match, don't proceed" outcome for write-oriented conditional headers (If-Match).
How do I use If-Match to prevent lost updates?
Fetch the resource, note its ETag header. When updating, include If-Match: <that etag> on your PUT/PATCH/DELETE request. If the resource hasn't changed since your fetch, the update proceeds normally. If it has changed (someone else updated it), you get 412, signaling you should re-fetch and reconcile before retrying.
What should I do after getting a 412?
Re-fetch the current state of the resource (including its new ETag), compare it with the changes you were trying to make, reconcile any conflicts, and retry your update using the new ETag in your If-Match header.
Is implementing 412/If-Match support common?
It's considered a best practice for APIs where concurrent modifications to the same resource are likely (collaborative tools, shared data), but it requires deliberate implementation — many simpler APIs don't support conditional writes and would simply allow the second update to silently overwrite the first (a "last write wins" approach) without any 412.
Fun fact
412 and 304 form a kind of matched pair representing two different philosophies for handling "has this changed?" — 304 optimizes for not re-downloading unchanged data (a read-side concern), while 412 protects against overwriting data that's changed unexpectedly (a write-side concern) — together, they're part of HTTP's relatively under-appreciated built-in toolkit for both performance and data-safety that predates many application-level solutions to the same problems.