HTTP
412 - Precondition Failed
Hitting a 412 Precondition Failed means your conditional request headers (If-Match, If-None-Match, If-Modified-Since, If-Unmodified-Since) didn't match the server's current resource state—the ETag changed, the resource was modified, or the version check failed. This client-side error (4xx) happens when optimistic concurrency control validates request preconditions and they fail. Most common during concurrent edits where your If-Match ETag is stale, but also appears when conditional GET requests use If-None-Match to check cache validity or If-Modified-Since to verify freshness.
#Common Causes
- →Frontend: Stale ETag used in If-Match header—resource was modified since last fetch. If-None-Match check fails because resource exists (used for "create only if not exists"). If-Modified-Since timestamp is older than resource modification time. Client cache has outdated version information.
- →Backend: ETag validation fails—resource version changed, ETag mismatch. If-Match evaluates to false (resource modified by another request). If-None-Match evaluates to false (resource exists when trying to create). Last-Modified timestamp comparison fails. Version field in database doesn't match request version.
- →Infrastructure: Load balancer routes to different backend instances with inconsistent state. Cache serves stale ETag values. Database replication lag causes temporary ETag inconsistencies. CDN cache returns outdated Last-Modified headers.
✓Solutions
- 1Step 1: Diagnose - Check DevTools Network tab Request Headers for conditional headers (If-Match, If-None-Match). Compare with Response Headers ETag value—do they match? Verify Last-Modified timestamps in request vs response.
- 2Step 2: Diagnose - Server logs show which precondition failed and current resource state. Review ETag generation logic—ensure consistency. Check database for concurrent modifications. Verify version numbers or timestamps.
- 3Step 3: Fix - Client-side: Refresh resource and get fresh ETag before retry. Implement conflict resolution UI—show both versions, let user merge. Use If-None-Match correctly (for create operations, not updates). Handle 412 gracefully with user feedback.
- 4Step 4: Fix - Server-side: Return current ETag in 412 response for easy refresh. Include resource state in error response to help conflict resolution. Ensure ETag generation is deterministic and consistent. Validate preconditions before processing request body.
- 5Step 5: Fix - Infrastructure: Ensure load balancer session affinity for stateful resources. Clear cache when resources are modified. Verify database replication lag is acceptable. Use distributed cache (Redis) for ETag consistency across instances.
</>Code Examples
Fetch API: Conditional Updates with Precondition Handling
1// Client-side: Handle 412 Precondition Failed
2async function updateResource(id, data, currentETag) {
3 const response = await fetch(`/api/resources/${id}`, {
4 method: 'PUT',
5 headers: {
6 'Content-Type': 'application/json',
7 'If-Match': currentETag, // Conditional update
8 },
9 body: JSON.stringify(data),
10 });
11
12 if (response.status === 412) {
13 // Precondition failed - resource was modified
14 const latestResponse = await fetch(`/api/resources/${id}`);
15 const latestData = await latestResponse.json();
16 const newETag = latestResponse.headers.get('ETag');
17
18 // Show conflict resolution UI
19 const userChoice = await showConflictDialog({
20 localChanges: data,
21 serverVersion: latestData,
22 newETag: newETag,
23 });
24
25 if (userChoice === 'use-local') {
26 // Retry with new ETag
27 return updateResource(id, data, newETag);
28 } else if (userChoice === 'use-server') {
29 // Use server version
30 return latestData;
31 } else {
32 // Merge changes
33 const merged = mergeChanges(data, latestData);
34 return updateResource(id, merged, newETag);
35 }
36 }
37
38 if (!response.ok) {
39 throw new Error(`Update failed: ${response.status}`);
40 }
41
42 return response.json();
43 }
44
45// Conditional create (only if doesn't exist)
46async function createResourceIfNotExists(data, currentETag) {
47 const response = await fetch('/api/resources', {
48 method: 'POST',
49 headers: {
50 'Content-Type': 'application/json',
51 'If-None-Match': '*', // Only create if resource doesn't exist
52 },
53 body: JSON.stringify(data),
54 });
55
56 if (response.status === 412) {
57 throw new Error('Resource already exists');
58 }
59
60 return response.json();
61}Express.js: Precondition Validation Middleware
1// Server-side: Validate conditional request headers
2const express = require('express');
3const crypto = require('crypto');
4const app = express();
5
6function generateETag(resource) {
7 const hash = crypto.createHash('md5')
8 .update(JSON.stringify(resource) + resource.updatedAt)
9 .digest('hex');
10 return `"${hash}"`;
11}
12
13// Middleware to validate preconditions
14const validatePreconditions = async (req, res, next) => {
15 const resource = await db.resources.findById(req.params.id);
16
17 if (!resource) {
18 return res.status(404).json({ error: 'Not Found' });
19 }
20
21 const currentETag = generateETag(resource);
22 const ifMatch = req.headers['if-match'];
23 const ifNoneMatch = req.headers['if-none-match'];
24 const ifModifiedSince = req.headers['if-modified-since'];
25 const ifUnmodifiedSince = req.headers['if-unmodified-since'];
26
27 // Validate If-Match (update only if ETag matches)
28 if (ifMatch && ifMatch !== '*' && ifMatch !== currentETag) {
29 return res.status(412)
30 .set('ETag', currentETag)
31 .json({
32 error: 'Precondition Failed',
33 message: 'Resource was modified. ETag mismatch.',
34 currentETag: currentETag,
35 providedETag: ifMatch,
36 });
37 }
38
39 // Validate If-None-Match (create only if doesn't exist)
40 if (ifNoneMatch && ifNoneMatch === currentETag) {
41 return res.status(412).json({
42 error: 'Precondition Failed',
43 message: 'Resource already exists',
44 });
45 }
46
47 // Validate If-Unmodified-Since
48 if (ifUnmodifiedSince) {
49 const requestedDate = new Date(ifUnmodifiedSince);
50 const resourceDate = new Date(resource.updatedAt);
51 if (resourceDate > requestedDate) {
52 return res.status(412)
53 .set('Last-Modified', resource.updatedAt.toUTCString())
54 .json({
55 error: 'Precondition Failed',
56 message: 'Resource was modified after the specified date',
57 });
58 }
59 }
60
61 // Set current ETag for response
62 res.set('ETag', currentETag);
63 res.set('Last-Modified', resource.updatedAt.toUTCString());
64
65 next();
66};
67
68// Apply to update route
69app.put('/api/resources/:id', validatePreconditions, async (req, res) => {
70 const updated = await db.resources.update(req.params.id, req.body);
71 const newETag = generateETag(updated);
72 res.set('ETag', newETag);
73 res.json(updated);
74});Nginx: Pass Conditional Headers for Precondition Checks
1# Nginx: Pass conditional headers to backend
2server {
3 listen 80;
4 server_name api.example.com;
5
6 location /api/resources/ {
7 proxy_pass http://backend;
8
9 # Pass all conditional headers to backend
10 proxy_set_header If-Match $http_if_match;
11 proxy_set_header If-None-Match $http_if_none_match;
12 proxy_set_header If-Modified-Since $http_if_modified_since;
13 proxy_set_header If-Unmodified-Since $http_if_unmodified_since;
14
15 # Pass ETag and Last-Modified from backend to client
16 proxy_pass_header ETag;
17 proxy_pass_header Last-Modified;
18
19 # Don't cache resources that use conditional headers
20 proxy_cache_bypass $http_if_match $http_if_none_match;
21
22 proxy_set_header Host $host;
23 proxy_set_header X-Real-IP $remote_addr;
24 }
25}↗Related Errors
Provider Information
This error code is specific to HTTP services. For more information, refer to the official HTTP documentation.