Skip to content
HTTP 412 Precondition Failed — What It Means & How to Debug

HTTP 412 Precondition Failed — What It Means & How to Debug

DodaTech Updated Jun 20, 2026 5 min read

HTTP 412 Precondition Failed means one or more conditional headers (If-Match, If-None-Match, If-Modified-Since) evaluated to false — used for optimistic concurrency.

What It Means

The HTTP 412 Precondition Failed status code is returned when a request includes conditional headers and the conditions specified by those headers are not met. Conditional headers allow clients to make requests that only succeed if certain preconditions are true, enabling optimistic concurrency control, cache validation, and safe updates.

The common conditional headers that can trigger a 412 are:

  • If-Match — the request should proceed only if the resource’s ETag matches the provided value
  • If-None-Match — the request should proceed only if the resource’s ETag does not match
  • If-Modified-Since — the request should proceed only if the resource has been modified since the given date
  • If-Unmodified-Since — the request should proceed only if the resource has NOT been modified since the given date
  • If-Range — used with Range requests to ensure the resource hasn’t changed

The most common use case is optimistic concurrency: when a client wants to update a resource, it includes an If-Match header with the ETag of the version it last fetched. If another client has modified the resource in the meantime, the ETag won’t match and the server returns 412 instead of overwriting the changes silently.

When It’s Sent

  • Concurrent update conflict — two clients fetch the same resource; one updates it, the other tries to update with a stale ETag
  • Conditional DELETE — a client deletes a resource only if it hasn’t been modified since it was last fetched
  • Conditional PUT — a client updates a resource only if the version it knows about is still current
  • Stale cache detection — a client with a cached version attempts an operation assuming the cache is fresh
  • ETag validation failure — a client sends an If-None-Match header and the current ETag matches (expected for cache validation but unexpected for updates)

Real Example

# First, fetch the resource to get its ETag
curl -v https://api.example.com/documents/42
> GET /documents/42 HTTP/1.1
> Host: api.example.com
>
< HTTP/1.1 200 OK
< ETag: "abc123"
< Content-Type: application/json
<
{"id":42,"title":"My Document","version":1}
# Try to update with a stale ETag (simulating concurrent modification)
curl -v -X PUT \
  -H "Content-Type: application/json" \
  -H "If-Match: \"stale-etag\"" \
  -d '{"title":"Updated Document"}' \
  https://api.example.com/documents/42

Expected response:

> PUT /documents/42 HTTP/1.1
> Host: api.example.com
> If-Match: "stale-etag"
> Content-Type: application/json
>
< HTTP/1.1 412 Precondition Failed
< Content-Type: application/json
< Content-Length: 89
<
{"error":"precondition_failed","message":"Document was modified by another user","current_etag":"def456"}

The client should re-fetch the resource, merge changes, and retry with the new ETag.

How to Debug

Client-Side

  1. Check the ETag mismatch — compare the ETag you sent in If-Match with the current ETag (often returned in the error response)
  2. Re-fetch the resource — GET the resource again to get the latest ETag and version
  3. Merge changes — apply your update on top of the latest version to avoid overwriting concurrent edits
  4. Retry with the new ETag — use the fresh ETag in the If-Match header
  5. Handle gracefully — show a merge conflict UI or retry notification instead of a generic error

Server-Side

  1. Check the resource’s current ETag — verify it matches what the client sent
  2. Review concurrency logic — ensure the server is computing ETags correctly and checking them on every mutation
  3. Check for concurrent requests — look in server logs for overlapping modifications to the same resource
  4. Verify ETag generation — ensure ETags change when the resource changes (based on content hash, version number, or timestamp)
  5. Test with and without conditional headers — confirm the update works without concurrency checks

curl

# Fetch the current ETag
ETAG=$(curl -sI https://api.example.com/doc/1 | grep -i etag | cut -d' ' -f2)

# Try updating with the current ETag (should succeed)
curl -v -X PUT -H "If-Match: $ETAG" \
  -d '{"title":"New"}' https://api.example.com/doc/1

# Try with a fake ETag (should return 412)
curl -v -X PUT -H 'If-Match: "fake-etag"' \
  -d '{"title":"New"}' https://api.example.com/doc/1

Postman

  1. Fetch the resource with a GET request
  2. Copy the ETag value from the response headers
  3. Send a PUT request with an If-Match header set to that ETag
  4. To trigger a 412, change the ETag value to something different and send again

Common Causes

ScenarioWhat HappensWhy It Matters
Concurrent editTwo users edit the same documentSecond save fails with 412 — user must refresh and merge
Stale client cacheClient cached an old version and tries to updateClient must re-fetch before editing
Missing ETag handlingClient doesn’t send If-Match and overwrites another user’s changesThe server should require conditional headers for writes
Wrong ETag formatClient sends If-Match: abc123 but server expects "abc123" (with quotes)Match the exact ETag format from the response
Session timeoutUser’s session expired and the resource was updated by another sessionRe-authenticate and re-fetch the latest version

FAQ

What is the difference between 412 and 409 Conflict?
412 Precondition Failed is specifically about conditional headers (ETag, If-Modified-Since) failing. The request would have succeeded if the preconditions were met. 409 Conflict is broader — it indicates any state conflict with the current resource state, such as a duplicate entry or a business rule violation. Use 412 for ETag-based concurrency and 409 for semantic conflicts.
Should I always use conditional headers for API updates?
Yes, if you want to prevent lost updates. Without If-Match, the last writer wins — which means a client with stale data can silently overwrite another client’s changes. Conditional headers enforce optimistic concurrency: if the resource changed since you last fetched it, the update fails and you must re-fetch, merge, and retry. This is standard practice for REST APIs handling mutable resources.
What should the client do after receiving a 412?
The client should re-fetch the resource (GET) to get the current state and ETag, then re-apply the changes on top of the fresh version and retry the update with the new ETag. This is analogous to “rebase and retry” in version control. The application should guide the user through resolving any merge conflicts rather than silently overwriting data.

Related Codes

  • HTTP 200 OK — the update succeeds when preconditions are met
  • HTTP 304 Not Modified — when If-None-Match or If-Modified-Since succeeds
  • HTTP 409 Conflict — broader conflict that includes business logic violations
  • HTTP 428 Precondition Required — the server requires conditional headers but the client did not send them

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro