412 Status Code (Precondition Failed): Meaning, Causes and How to Fix It
Published · Vettaly
Before publishing
Add the Vettaly crawl report screenshot (placeholder in "How to Find 412 Errors").
Confirm how the full-site crawl report labels pages that return 4xx. The homepage doesn't state it, so the article only says such pages are "flagged".
The "polite by design" link points to the homepage, because the Polite crawling block has no anchor. Add one (for example
#crawling) and update the link.Curl output comes from a real Nginx 1.24 test run on Oct 11, 2026; keep it as is.
What Is the 412 Status Code (Precondition Failed)?
A 412 Precondition Failed response means the server checked a condition attached to your request, found it false, and refused to run the request. The condition is usually something like "only save this if the page is still version abc123". 412 is a 4xx client error, defined in RFC 9110, section 15.5.13.
Nothing has crashed. The server is working and deliberately said no, because acting on outdated information could overwrite someone else's change.
The 60-second checklist
Look for conditional headers in the request. In standard HTTP, a 412 comes from
If-MatchorIf-Unmodified-Since, or fromIf-None-Matchon a request other than GET or HEAD. Some APIs add their own, such as Google Cloud Storage'sifGenerationMatch.Compare the validator with the server's current one. If the
ETagor date you sent no longer matches the server's, fetch the resource again and retry with the fresh value.Check what sits in between. A CDN, proxy or compression layer can change the ETag, for example into a weak
W/"..."value, so a correct-looking header still fails.Just visiting a site? Reload the page, clear that site's cookies and cache, and try again. If it keeps happening, the site owner has to fix it.
How a 412 Error Happens: Conditional Requests Explained
A conditional request tells the server: "do this only if the resource is still the version I saw." The server checks this against a validator, either an ETag (an opaque version tag) or a Last-Modified date.
Here is the classic case. Two editors open the same page in a CMS, and both receive ETag: "abc123". Editor A saves first, so the server's ETag becomes "def456". Then editor B saves:
PUT /api/pages/42 HTTP/1.1
Host: cms.example.com
If-Match: "abc123"
Content-Type: application/json
{"title": "Pricing (updated)"}HTTP/1.1 412 Precondition Failed
ETag: "def456"Without the precondition, B's save would silently overwrite A's work. This is the "lost update" problem that conditional requests exist to prevent. With it, B gets a 412, reloads the latest version, merges and saves again.
Which headers can trigger a 412
Only some conditional headers can produce a 412. Two of them, often listed alongside, never do.
Header | What it asks | If the condition is false |
|---|---|---|
| Is the current ETag one of these? (strong comparison) | 412 |
| Unchanged since this date? (ignored when | 412 |
| Is the current ETag none of these? | 304 on GET/HEAD, 412 on other methods, e.g. |
| Changed since this date? (GET/HEAD only) | 304, never 412 |
| Is my partial download still current? | The server ignores |
Reproduce a 412 with curl
You can trigger a 412 on any server that honours conditional requests. We ran the commands below against a stock Nginx 1.24 test server serving a static page. Swap in your own URL.
First, get the current ETag:
curl -sI http://localhost:8080/pricing.html | grep -i etag
# ETag: "6acaf199-9c2"Then send an outdated one:
curl -v -H 'If-Match: "abc123"' http://localhost:8080/pricing.htmlThe real response (trimmed):
> GET /pricing.html HTTP/1.1
> If-Match: "abc123"
>
< HTTP/1.1 412 Precondition Failed
< Server: nginx/1.24.0 (Ubuntu)
< Content-Type: text/html
< Content-Length: 182Send the current value, If-Match: "6acaf199-9c2", and the same request returns 200 OK. An If-Unmodified-Since date older than the file's Last-Modified also returns 412, while a later date returns 200.
Common Causes of a 412 Error
Most 412s trace back to one of four things. Each has a different place to look first.
Cause | Where to look first |
|---|---|
The validator went stale, or servers disagree on it | Compare the request's ETag with a fresh |
A precondition was sent that shouldn't be | The outgoing request headers from your client, SDK or gateway |
A proxy, CDN or compression layer changed the ETag | Compare ETags from the origin and from the CDN |
The server or WAF is configured inconsistently | Which layer produced the 412: its |
Stale or Mismatched ETags and Validators
The most common cause is simple: the resource changed after the client read its ETag. Another user, a background job or a deploy updated it, so the If-Match no longer matches. The fix is to GET the resource again and retry with the new ETag.
The sneakier version happens behind a load balancer. Nginx builds static-file ETags from the file's modification time and size, so the same file deployed at different times gets different ETags. In our test, two Nginx servers serving byte-identical files returned "6acaf199-9c2" and "6acaf250-9c2". An If-Match with the first value, routed to the second server, got a 412.
Apache has the same risk if its FileETag setting includes INode, because inode numbers differ between machines. Apache 2.4 defaults to FileETag MTime Size, but older configs may still use INode or All. The fix is one ETag strategy across every server: content-based hashes, or deploys that preserve file timestamps.
Incorrect or Unnecessary Precondition Headers
Client libraries, SDKs and API gateways can add or replay conditional headers without the developer noticing. A cached ETag reused on a write is a common example. Log the headers your client actually sends, and remove any precondition the feature doesn't need.
Dates cause the rest. RFC 9110 says a server must ignore an If-Unmodified-Since value that isn't a valid HTTP date, but not every server does. In our test, Nginx 1.24 answered If-Unmodified-Since: 2024-01-01 with a 412. Always send the full format, such as Sun, 11 Oct 2026 02:16:57 GMT, in GMT. Better still, copy the server's own Last-Modified value rather than generating a date from a local clock.
Proxies, CDNs and Caching Layers
A CDN or proxy can strip or rewrite the ETag, or serve a cached response carrying an older one. The client then sends a validator the origin has never issued.
Compression is the trap most people miss. When Nginx gzips a response, it turns the ETag into a weak one: ETag: W/"6acaf199-9c2". If-Match requires a strong comparison, so a client that echoes that weak value back fails. In our test, If-Match: W/"6acaf199-9c2" returned 412, while the strong "6acaf199-9c2" returned 200. Apache's mod_deflate instead appends the encoding to the ETag, such as -gzip, by default.
To fix it, compare the ETag at each hop: request the origin directly, then through the CDN. Keep caching, compression or ETag rewriting off the routes whose ETags drive writes, such as your API.
Server and Application Misconfiguration
A WAF, reverse proxy or framework can mishandle conditional requests, or two layers can both evaluate them. First find out which layer answered. Nginx's default 412 page names Nginx in its footer and Server header, while an app usually returns JSON.
In our test, Nginx enforced If-Match on static files it served itself but passed proxied responses through untouched. So a 412 from a proxied API endpoint came from the app, not Nginx.
Nginx checkpoints
etag on | off: static-file ETags are built from modification time and size, so keep timestamps identical across servers (for examplersync -t).gzip on: ETags become weak on compressed responses, and weak ETags always failIf-Match.Routing: files served from
rootortry_filesare judged by Nginx;proxy_passroutes are judged by your app.
Apache checkpoints
FileETag: useMTime SizeorDigest, notINode, on multi-server setups.DeflateAlterETag(Apache 2.4.58+):AddSuffixis the default;NoChangekeeps the original ETag,Removedrops it. See the mod_deflate docs.WAF rules: check the mod_security or host firewall audit log for rules that deny requests with a custom status.
How to Fix a 412 Error: Step by Step
The fix depends on which side of the request you're on. Visitors can only refresh their own state; developers and site owners fix the cause.
If you're visiting a website
Hard-reload the page (Ctrl+Shift+R, or Cmd+Shift+R on Mac) to get a fresh copy.
Clear cookies and cached files for that site only, then try again.
Open the page in a private window or another browser, with extensions off. Privacy and ad-blocking extensions can change request headers.
If a form failed, copy what you typed before reloading, then submit again a few minutes later.
Still failing? Send the site owner the URL and the time it happened. The fix is on their side.
If you're a developer
Capture the real request. Use
curl -v, or open DevTools → Network → the failing request → Request Headers. Note everyIf-*header.Fetch the current validator. Run
curl -sI <url>and compare itsETagorLast-Modifiedwith what the request sent.Bypass the middle layers. Repeat the request against the origin directly. If the origin returns 200 and the CDN returns 412, the problem is in between.
Remove preconditions you don't need. A read-only page or a feature without concurrent edits rarely needs
If-Match.
If you're calling an API
In an API that uses optimistic concurrency, a 412 is a normal signal, not a bug. It means someone changed the resource since you read it. The standard handling is:
GET /api/pages/42 → 200, ETag: "def456"
(apply your change to the fresh copy)
PUT /api/pages/42
If-Match: "def456" → 200 OK, or 412 again → repeat (cap at ~3 tries)Never retry the same request with the same ETag: it will fail again. To create a resource without overwriting an existing one, send If-None-Match: *, which returns 412 if it already exists.
If you run the website
Find out where the 412s are and who receives them. With the common combined log format, the status code is field 9:
# all 412 responses
awk '$9 == 412' /var/log/nginx/access.log
# which paths return 412 most often
awk '$9 == 412 {print $7}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | headThen check the user agents on those lines. A 412 served to a browser is a bug report; a 412 served to Googlebot is an SEO problem.
412 vs 400, 409, 428 and 304: What's the Difference?
These five codes are often confused because they can all appear around the same write. The difference is what went wrong, and what the client should do next.
Code | Name | What it means | Typical trigger | What the client should do |
|---|---|---|---|---|
400 | Bad Request | The server can't parse or accept the request itself | Broken JSON, invalid syntax, malformed headers | Fix the request |
409 | Conflict | The request clashes with the resource's current state | Creating a username that exists; an invalid state change | Resolve the conflict, then retry |
412 | Precondition Failed | A condition the client attached was false |
| Re-fetch, merge, retry with the fresh validator |
428 | Precondition Required | The server requires a conditional request, and none was sent | A PUT without | Add |
304 | Not Modified | A conditional GET found your cached copy is still current |
| Use the cached copy; this is success, not an error |
428 is defined separately, in RFC 6585. APIs use it to force clients into the safe, conditional pattern that 412 then enforces.
Rule of thumb for API builders: can't parse it → 400. No precondition, but you require one → 428. Precondition sent and false → 412. Precondition fine (or none), but the change breaks a business rule → 409. Conditional read and nothing changed → 304.
Does a 412 Error Hurt SEO?
A 412 only hurts SEO if search engines receive it, and on a healthy site they rarely do. Crawlers fetch pages with plain GET requests and don't send If-Match or If-Unmodified-Since.
Google's crawlers do revalidate cached pages with If-None-Match and If-Modified-Since. Those return 304 when nothing changed, or 200 when it did, never 412.
If Googlebot does get a 412, for example from a misconfigured CDN or WAF, Google treats it like any other 4xx. According to Google's documentation:
Google ignores the content of URLs that return a 4xx code and doesn't index them.
URLs already in the index that start returning a 4xx are removed from it.
4xx codes other than 429 don't change Google's crawl rate.
So a public page that keeps answering 412 doesn't get demoted. It drops out of search results entirely.
To stay safe, make sure every public page returns 200 to a plain GET. Then check it the way a crawler revalidates: the request below should return 200 or 304, never 412.
curl -s -o /dev/null -w '%{http_code}\n' -H 'If-None-Match: "x"' https://your-site.com/pageIf affected pages also have indexing or canonical issues, Vettaly's indexation rules cover those checks.
How to Find 412 Errors on Your Site
Three sources together give the full picture: your logs show every 412, Search Console shows what Google saw, and a crawl shows what any visitor would hit.
1. Server and CDN logs
Logs are the only place you'll see 412s returned to API clients and logged-in users. Use the awk commands from the fix section above, and check your CDN's logs too, since a CDN can return a 412 that never reaches your origin.
2. Google Search Console
Two reports matter here:
Settings → Crawl stats → By response shows how Google's requests split across status codes. Rare 4xx codes like 412 are grouped as other client errors.
Indexing → Pages lists URLs Google couldn't index. A 412 shows up under Blocked due to other 4xx issue.
Search Console only covers what Googlebot requested, and the data lags by days. Use it to confirm the impact, not to find every case.
3. A full-site crawl with Vettaly
A crawl requests each page the way a search engine does, so it catches pages that answer a plain GET with an error instead of a 200.
Start with your most important pages. Paste a URL into the free single-page audit. It needs no registration, runs one page a day, and keeps the report for 24 hours. Vettaly records every redirect hop and the final response, so you see exactly what a crawler gets.
For the whole site, sign up free and run a full-site crawl: 30 pages per audit on the Free plan, up to 2,000 on Pro. Pages that return an error are flagged in the report. Internal links that lead to them are checked by the broken internal links rule (links.internal.broken) in the Links category.
The crawl is polite by design: it honours robots.txt and Crawl-delay, opens at most 5 concurrent requests per domain, and waits 200–500 ms between them. It won't trip your rate limits or WAF while it looks for problems.
[Author: add a screenshot of a Vettaly full-site crawl report showing a URL flagged with a 4xx status, plus the 2–3 clicks to get there. Confirm the exact label the report uses for non-200 pages.]
FAQ
What does 412 Precondition Failed mean?
It means a condition attached to the request, such as If-Match or If-Unmodified-Since, was false when the server checked it, so the server refused to act. It's a 4xx client error defined in RFC 9110. Most often, the resource changed since you last loaded it.
How do I fix a 412 error?
As a visitor, hard-reload the page, clear that site's cookies and cache, or try a private window. As a developer, compare the ETag or date your request sent with the server's current one, fetch the latest version, and retry with the fresh value. If that fails, check whether a CDN or compression layer is changing the ETag.
What is the difference between 412 and 400?
A 400 means the server couldn't understand or accept the request itself, for example malformed JSON. A 412 means the request was fine, but a condition in it evaluated to false. You fix a 400 by correcting the request, and a 412 by refreshing the validator.
What is the difference between 412 and 409?
A 412 is triggered by an explicit precondition header that turned out false. A 409 means the request conflicts with the resource's current state, with or without any precondition, such as creating a duplicate username. APIs with ETags typically use 412 for stale versions and 409 for business-rule conflicts.
What causes a 412 error on a website I'm only visiting?
Usually it's stale state in your browser: a cached page or form that is older than the server's copy, or an extension changing request headers. Occasionally, the site's CDN or security layer is misconfigured. Reload, clear the site's cookies, or try a private window; if it persists, only the site owner can fix it.
Can a 412 status code affect SEO or indexing?
Only if search engines receive it. Google doesn't index URLs that return a 4xx code and removes already-indexed URLs that start returning one, though 4xx codes other than 429 don't change crawl rate. Make sure every public page answers a plain GET with 200.
What is the 412 error in Google or Google Cloud?
In Google Cloud Storage, a 412 means a request precondition failed. Typical ones are ifGenerationMatch (x-goog-if-generation-match in the XML API), ifMetagenerationMatch or If-Match, usually because the object changed since you read its generation. Re-read the object's current generation and retry. If you see a 412 in a Google app in your browser, treat it as stale session state: reload, sign out and in, or clear that site's data.
What is the difference between 412 and 428?
A 428 Precondition Required means the server demands a conditional request and you didn't send one. A 412 means you sent one and it failed. Fix a 428 by fetching the resource and adding its ETag in If-Match.
How do I test for a 412 with curl?
Get the current ETag with curl -sI <url> | grep -i etag. Then send a wrong one and print only the status code: curl -s -o /dev/null -w '%{http_code}\n' -H 'If-Match: "stale"' <url>. A server that honours If-Match returns 412; add -v to see the full request and response headers.