- Home
- HTTP Status Codes
- 412 Precondition Failed
Status Code
412 Precondition Failed
The server doesn't meet one or more preconditions specified in request headers. Learn about conditional requests and how to prevent conflicts.
What is 412 Precondition Failed?
TL;DR: Your If-Match or conditional headers don’t match current resource state. Fetch the latest version and retry with updated ETag.
A 412 Precondition Failed status code means one or more conditions specified in the request headers were not met by the server. Think of it like a safety deposit box that requires two keys—if you only have one key, the precondition fails and you can’t access the box.
This is commonly used for conditional requests to prevent race conditions, lost updates, and ensure data consistency in collaborative environments.
When Does This Happen?
You’ll see a 412 Precondition Failed response in these common situations:
1. ETag Mismatch (Optimistic Locking)
Client tries to update outdated version
If-Match: "old-etag" → Resource changed → 412
2. If-Unmodified-Since Check
Client requires resource unchanged since date
If-Unmodified-Since: Mon, 01 Jan 2026 00:00:00 GMT → Modified → 412
3. If-None-Match Precondition
Client wants to create only if not exists
If-None-Match: * → Resource exists → 412
4. Range Request Precondition
Partial content request on modified file
If-Range: "etag-123" → ETag changed → 412
5. WebDAV Lock Token Mismatch
Client tries to modify locked resource
If: (<lock-token>) → Wrong token → 412
Example Responses
ETag Mismatch:
HTTP/1.1 412 Precondition Failed
Content-Type: application/json
ETag: "current-etag-xyz"
Last-Modified: Sat, 18 Jan 2026 14:30:00 GMT
{
"error": "Precondition Failed",
"message": "Resource has been modified since your last request",
"current_etag": "current-etag-xyz",
"provided_etag": "old-etag-abc",
"action": "Fetch the latest version and retry your update"
}
```text
**If-Unmodified-Since Failed:**
```http
HTTP/1.1 412 Precondition Failed
Content-Type: application/json
Last-Modified: Sat, 18 Jan 2026 12:00:00 GMT
{
"error": "Precondition Failed",
"message": "Resource was modified after specified date",
"requested_date": "2026-01-18T10:00:00Z",
"last_modified": "2026-01-18T12:00:00Z"
}
If-None-Match Failed (Resource Exists):
HTTP/1.1 412 Precondition Failed
Content-Type: application/json
ETag: "existing-resource-etag"
Location: /api/documents/doc-123
{
"error": "Precondition Failed",
"message": "Resource already exists",
"etag": "existing-resource-etag",
"resource_url": "/api/documents/doc-123"
}
```text
## Real-World Example
Imagine two users simultaneously editing the same document:
**User A Fetches Document:**
```http
GET /api/documents/doc-456 HTTP/1.1
Host: api.example.com
Response:
HTTP/1.1 200 OK
ETag: "version-100"
Last-Modified: Sat, 18 Jan 2026 10:00:00 GMT
{
"id": "doc-456",
"title": "Project Proposal",
"content": "Original content",
"version": 100
}
User B Updates Document (succeeds):
PUT /api/documents/doc-456 HTTP/1.1
Host: api.example.com
If-Match: "version-100"
Content-Type: application/json
{
"title": "Project Proposal - Updated",
"content": "User B's changes"
}
Response:
HTTP/1.1 200 OK
ETag: "version-101"
```text
**User A Tries to Update (fails - outdated version):**
```http
PUT /api/documents/doc-456 HTTP/1.1
Host: api.example.com
If-Match: "version-100" ← Outdated ETag
Content-Type: application/json
{
"title": "Project Proposal - My Changes",
"content": "User A's changes"
}
Response:
HTTP/1.1 412 Precondition Failed
Content-Type: application/json
ETag: "version-101" ← Current version
{
"error": "Precondition Failed",
"message": "Document has been modified by another user",
"conflict_details": {
"your_version": "version-100",
"current_version": "version-101",
"modified_by": "user-b",
"modified_at": "2026-01-18T10:05:00Z"
},
"action": "Fetch the latest version and merge your changes",
"current_document_url": "/api/documents/doc-456"
}
412 vs Other Conditional Response Codes
| Code | Meaning | Condition Type | Action Required |
|---|---|---|---|
| 412 | Precondition failed | If-Match, If-Unmodified-Since | Fetch latest and retry |
| 304 | Not modified | If-None-Match, If-Modified-Since | Use cached version |
| 428 | Precondition required | Missing precondition headers | Add conditional headers |
| 409 | Conflict | Business logic conflict | Resolve conflict manually |
Important Characteristics
Conditional Request Headers:
If-Match: "etag-value" ← Must match current ETag
If-None-Match: "etag-value" ← Must NOT match
If-Unmodified-Since: <date> ← Must not be modified after date
If-Modified-Since: <date> ← Must be modified after date (304)
If-Range: "etag-value" ← For partial content requests
```text
**Optimistic Locking Pattern:**
```text
1. Client fetches resource with ETag: "v1"
2. Another client updates, ETag becomes "v2"
3. First client tries to update with If-Match: "v1"
4. Server returns 412 (precondition failed)
5. Client fetches latest version and retries
```text
**Safe Operations:**
```http
GET with If-None-Match → 304 Not Modified (OK)
PUT with If-Match → 412 Precondition Failed (prevents lost updates)
```text
## Common Mistakes
**❌ Not using ETags for update operations**
```http
PUT /api/resource/123
Content-Type: application/json
{"data": "updated"} ← No If-Match header, risky!
❌ Returning 412 when 304 is appropriate
GET /resource
If-None-Match: "current-etag" ← Matches current
HTTP/1.1 412 Precondition Failed ← Wrong! Should be 304
```text
**❌ Not providing current ETag in 412 response**
```http
HTTP/1.1 412 Precondition Failed
Content-Type: text/plain
Precondition failed. ← Unhelpful, no current ETag provided
✅ Correct usage with helpful information
HTTP/1.1 412 Precondition Failed
Content-Type: application/json
ETag: "current-version-etag"
{
"error": "Precondition Failed",
"current_etag": "current-version-etag",
"fetch_url": "/api/resource/123"
}
```javascript
## Best Practices
**Always Use ETags for Updates:**
```javascript
// Express.js example
app.put('/api/documents/:id', async (req, res) => {
const doc = await Document.findById(req.params.id)
const currentETag = `"${doc.version}"`
const clientETag = req.headers['if-match']
if (!clientETag) {
return res.status(428).json({
error: 'Precondition Required',
message: 'If-Match header is required for updates'
})
}
if (clientETag !== currentETag) {
return res.status(412).set('ETag', currentETag).json({
error: 'Precondition Failed',
message: 'Document has been modified',
current_etag: currentETag,
provided_etag: clientETag
})
}
// Proceed with update
await doc.update(req.body)
res.set('ETag', `"${doc.version + 1}"`).json(doc)
})
Provide Clear Error Messages:
HTTP/1.1 412 Precondition Failed
Content-Type: application/json
ETag: "v25"
{
"error": "Precondition Failed",
"message": "Resource has been modified since you last fetched it",
"details": {
"your_version": 20,
"current_version": 25,
"changes_count": 5,
"last_modified_by": "alice@example.com",
"last_modified_at": "2026-01-18T14:30:00Z"
},
"actions": {
"fetch_latest": {
"method": "GET",
"url": "/api/documents/doc-123",
"description": "Fetch the current version"
},
"view_history": {
"method": "GET",
"url": "/api/documents/doc-123/history?from=20&to=25",
"description": "See what changed"
}
}
}
```text
**Include Current Resource State:**
```http
HTTP/1.1 412 Precondition Failed
Content-Type: application/json
ETag: "current-etag"
{
"error": "Precondition Failed",
"current_state": {
"etag": "current-etag",
"version": 42,
"last_modified": "2026-01-18T14:30:00Z"
},
"your_request": {
"etag": "old-etag",
"version": 40
}
}
Conditional Request Patterns
Optimistic Locking:
// Client-side example
async function updateDocument(id, changes) {
// 1. Fetch current version
const response = await fetch(`/api/documents/${id}`)
const doc = await response.json()
const etag = response.headers.get('ETag')
// 2. Apply changes
const updated = { ...doc, ...changes }
// 3. Update with If-Match
const updateResponse = await fetch(`/api/documents/${id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'If-Match': etag // Conditional update
},
body: JSON.stringify(updated)
})
if (updateResponse.status === 412) {
// Handle conflict
console.log('Document was modified, fetching latest...')
return updateDocument(id, changes) // Retry with latest
}
return updateResponse.json()
}
```text
**Prevent Duplicate Creation:**
```http
PUT /api/users/alice HTTP/1.1
If-None-Match: * ← Only create if doesn't exist
Content-Type: application/json
{"name": "Alice", "email": "alice@example.com"}
Response if exists:
HTTP/1.1 412 Precondition Failed
Location: /api/users/alice
ETag: "existing-user-etag"
Implementation Examples
Express.js:
const generateETag = (data) => {
return `"${crypto.createHash('md5').update(JSON.stringify(data)).digest('hex')}"`
}
app.put('/api/resources/:id', async (req, res) => {
const resource = await Resource.findById(req.params.id)
const currentETag = generateETag(resource)
const ifMatch = req.headers['if-match']
if (ifMatch && ifMatch !== currentETag) {
return res.status(412).set('ETag', currentETag).json({
error: 'Precondition Failed',
current_etag: currentETag,
provided_etag: ifMatch
})
}
await resource.update(req.body)
const newETag = generateETag(resource)
res.set('ETag', newETag).json(resource)
})
```javascript
**Django:**
```python
from django.http import JsonResponse
from django.views.decorators.http import require_http_methods
import hashlib
import json
@require_http_methods(["PUT"])
def update_resource(request, resource_id):
resource = Resource.objects.get(id=resource_id)
current_etag = hashlib.md5(
json.dumps(resource.to_dict()).encode()
).hexdigest()
if_match = request.META.get('HTTP_IF_MATCH', '').strip('"')
if if_match and if_match != current_etag:
return JsonResponse({
'error': 'Precondition Failed',
'current_etag': f'"{current_etag}"',
'provided_etag': f'"{if_match}"'
}, status=412, headers={'ETag': f'"{current_etag}"'})
# Proceed with update
resource.update(**request.POST.dict())
new_etag = hashlib.md5(
json.dumps(resource.to_dict()).encode()
).hexdigest()
return JsonResponse(
resource.to_dict(),
headers={'ETag': f'"{new_etag}"'}
)
ASP.NET Core:
[HttpPut("api/resources/{id}")]
public async Task<IActionResult> UpdateResource(int id, [FromBody] ResourceDto dto)
{
var resource = await _context.Resources.FindAsync(id);
var currentETag = GenerateETag(resource);
var ifMatch = Request.Headers["If-Match"].ToString();
if (!string.IsNullOrEmpty(ifMatch) && ifMatch != currentETag)
{
Response.Headers["ETag"] = currentETag;
return StatusCode(412, new
{
error = "Precondition Failed",
current_etag = currentETag,
provided_etag = ifMatch
});
}
// Update resource
resource.Update(dto);
await _context.SaveChangesAsync();
var newETag = GenerateETag(resource);
Response.Headers["ETag"] = newETag;
return Ok(resource);
}
Try It Yourself
Visit our request builder and trigger a 412 response:
- Set method to PUT
- Set path to /api/documents/1
- Add header
If-Match: "old-etag" - Click Send request
- See 412 with current ETag and conflict details
Related Status Codes
- 304 Not Modified - Conditional GET succeeded (not modified)
- 428 Precondition Required - Server requires conditional headers
- 409 Conflict - Request conflicts with current state
- 200 OK - Successful conditional request
Frequently Asked Questions
What does 412 Precondition Failed mean?
A 412 error means a condition specified in the request headers (like If-Match or If-Unmodified-Since) was not met. The server refuses to perform the request because preconditions failed.
What causes a 412 error?
Common causes include ETag mismatch with If-Match header, resource modified since If-Unmodified-Since date, or If-None-Match condition not satisfied.
How do I fix a 412 error?
Fetch the latest version of the resource to get the current ETag, then retry your request with the updated ETag in the If-Match header.
What is the difference between 412 and 409?
412 means explicit precondition headers failed. 409 means the request conflicts with resource state without using precondition headers. 412 is for conditional requests; 409 is for state conflicts.