HTTP 412 Precondition Failed
A conditional request header (If-Match, If-Unmodified-Since) didn’t match the current state of the resource.
Meaning
412 protects against lost updates: you sent If-Match: <etag> and someone else changed the resource since you read it. Object stores (S3, Azure Blob, GCS) and many REST APIs use it for optimistic concurrency.
Common causes
- Resource modified by another client since your read (ETag changed)
- Stale ETag cached by the client
- Weak vs strong ETag mismatch (
W/"...") - S3/Azure conditional writes (
If-None-Match: *when the object already exists)
⚡ Quick fix
- Re-fetch the resource, re-apply your change, and retry with the new ETag
- Show users a “this item changed” message instead of overwriting
- Make sure proxies don’t rewrite ETags (gzip can turn strong ETags weak)
Detailed fix by platform
REST API
- Optimistic update loop:javascript
async function update(id, change) { for (let attempt = 0; attempt < 3; attempt++) { const cur = await fetch(`/api/docs/${id}`); const etag = cur.headers.get('ETag'); const doc = { ...(await cur.json()), ...change }; const res = await fetch(`/api/docs/${id}`, { method: 'PUT', headers: { 'If-Match': etag, 'Content-Type': 'application/json' }, body: JSON.stringify(doc) }); if (res.status !== 412) return res; } throw new Error('Document keeps changing — try again later'); }
How to diagnose
- Condition — Which If-* header did you send?
- Current state — What is the current ETag/Last-Modified?
- Concurrency — Did another client write in between?
🧠 Still stuck? Analyze your error
Paste the full message, response headers or stack trace — we'll detect the platform and point to the most likely cause.
Was this page helpful?
Report a correction or suggest an improvement
Last updated 2 Oct 2026