HTTP 412 Precondition Failed — What It Means & How to Debug
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 valueIf-None-Match— the request should proceed only if the resource’s ETag does not matchIf-Modified-Since— the request should proceed only if the resource has been modified since the given dateIf-Unmodified-Since— the request should proceed only if the resource has NOT been modified since the given dateIf-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-Matchheader 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/42Expected 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
- Check the ETag mismatch — compare the ETag you sent in
If-Matchwith the current ETag (often returned in the error response) - Re-fetch the resource — GET the resource again to get the latest ETag and version
- Merge changes — apply your update on top of the latest version to avoid overwriting concurrent edits
- Retry with the new ETag — use the fresh ETag in the
If-Matchheader - Handle gracefully — show a merge conflict UI or retry notification instead of a generic error
Server-Side
- Check the resource’s current ETag — verify it matches what the client sent
- Review concurrency logic — ensure the server is computing ETags correctly and checking them on every mutation
- Check for concurrent requests — look in server logs for overlapping modifications to the same resource
- Verify ETag generation — ensure ETags change when the resource changes (based on content hash, version number, or timestamp)
- 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/1Postman
- Fetch the resource with a GET request
- Copy the
ETagvalue from the response headers - Send a PUT request with an
If-Matchheader set to that ETag - To trigger a
412, change the ETag value to something different and send again
Common Causes
| Scenario | What Happens | Why It Matters |
|---|---|---|
| Concurrent edit | Two users edit the same document | Second save fails with 412 — user must refresh and merge |
| Stale client cache | Client cached an old version and tries to update | Client must re-fetch before editing |
| Missing ETag handling | Client doesn’t send If-Match and overwrites another user’s changes | The server should require conditional headers for writes |
| Wrong ETag format | Client sends If-Match: abc123 but server expects "abc123" (with quotes) | Match the exact ETag format from the response |
| Session timeout | User’s session expired and the resource was updated by another session | Re-authenticate and re-fetch the latest version |
FAQ
Related Codes
- HTTP 200 OK — the update succeeds when preconditions are met
- HTTP 304 Not Modified — when
If-None-MatchorIf-Modified-Sincesucceeds - 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