If-Match

Concurrent updates to a shared resource risk overwriting each other. The HTTP If-Match request header prevents this by making a request conditional on the target resource matching one of the provided ETag values.

Usage

The If-Match header enables conditional requests by comparing ETag values. A client includes this header to ensure the server processes the request only when the resource's current entity tag matches one of the listed values.

For safe methods like GET and HEAD, the If-Match header restricts the response to resources with a matching entity tag. This selects a specific representation rather than validating a cache (cache validation uses If-None-Match instead).

For state-changing methods like PUT and DELETE, the If-Match header prevents the lost update problem. When two clients fetch the same resource, modify their copies independently, and attempt to write back, the If-Match header ensures only the first write succeeds. The second write fails with 412 Precondition Failed because the ETag changed after the first write.

This pattern is known as optimistic concurrency control. The client reads a resource and its ETag, makes changes locally, and submits the update with If-Match set to the original ETag. The server confirms the resource has not changed in the meantime before applying the update.

The If-Match header performs strong comparison only. Weak entity tags (prefixed with W/) do not match, even when the opaque value is identical. This strict behavior ensures byte-level equivalence of the resource.

Values

Entity tag list

One or more ETag values enclosed in double quotes and separated by commas. The server checks each value against the resource's current entity tag.

Wildcard (*)

The asterisk * matches any current representation of the resource. This form succeeds as long as the target resource exists, regardless of its entity tag value. A * value is commonly used with PUT to make the request update-only: the write fails with 412 when no resource exists at the target URI, so the PUT never creates one.

Example

A client sends a PUT request to update a resource. The If-Match header carries the ETag from the original GET response. The server applies the update only when the ETag still matches.

PUT /document/42 HTTP/1.1
Host: api.example.re
Content-Type: application/json
If-Match: "a1b2c3d4"

{"title": "Updated Document"}

When the resource has been modified by another client, the ETag no longer matches. The server responds with 412 Precondition Failed and rejects the update.

HTTP/1.1 412 Precondition Failed
Content-Type: application/json
ETag: "e5f6g7h8"

Multiple entity tags are listed as comma-separated values. The condition succeeds when any one of the listed values matches the resource's current ETag.

If-Match: "a1b2c3d4", "x9y8z7w6"

The wildcard form succeeds when the resource exists. This prevents a PUT from accidentally creating a new resource.

If-Match: *

Why If-Match fails in production

Strong comparison makes If-Match sensitive to any layer rewriting the ETag between the read and the write, and several common layers do.

Compression is the most frequent cause. Servers applying gzip or Brotli often rewrite or weaken the entity tag for the encoded representation, and a weak tag never satisfies If-Match. A client reading an uncompressed representation and writing back through a compressing proxy sends a tag the origin no longer recognizes, producing 412 Precondition Failed on a resource nobody else touched.

CDNs and reverse proxies introduce the same problem when they generate their own entity tags. Load balancing across origins generating tags independently produces a different value per node, so the failure appears intermittent and tracks whichever backend served the read.

Handling 412 correctly separates a working implementation from a retry loop. The response means the resource moved on, so the client refetches, reapplies the change against the current state, and submits again with the new tag. Repeating the original request with the original tag fails identically every time.

The * value asks a different question. Rather than matching a specific version, * requires the resource to exist at all, which turns a PUT into an update-only operation refusing to create. Pairing * on If-None-Match gives the reverse, a create-only operation refusing to overwrite.

See also

Last updated: August 17, 2026