ETag caching response
A unique identifier for a specific version of a resource — used for cache validation and optimistic concurrency control.
What it does
ETag (Entity Tag) is an opaque identifier the server assigns to a specific version of a resource. When the resource changes, the ETag changes. Clients store the ETag and send it back on subsequent requests — the server checks if the resource has changed and either returns a 304 (unchanged, use your cached copy) or a fresh 200 with the new content.
This powers two distinct use cases: cache validation (avoiding re-downloading unchanged content) and optimistic concurrency control (preventing one client from overwriting another's changes).
Syntax
ETag: "<etag-value>"
ETag: W/"<etag-value>"
Examples:
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
ETag: "abc123"
ETag: W/"weak-etag-value"
The value is always quoted. The W/ prefix marks a weak ETag.
Strong vs weak ETags
| Type | Syntax | Meaning |
|---|---|---|
| Strong | "abc123" |
Byte-for-byte identical. Two responses with the same strong ETag have identical bodies. |
| Weak | W/"abc123" |
Semantically equivalent. The content is the same for practical purposes but may differ in minor ways (whitespace, timestamps in headers, etc.). |
Strong ETags are required for range requests — you can't safely resume a partial download if you can't guarantee byte-level identity. Weak ETags are fine for cache validation where you just care about "is this the same content conceptually."
How cache validation works
The flow with ETags:
- Client fetches a resource; server responds with
ETag: "v1"andCache-Control: no-cache - Client caches the response and the ETag
- Later, client wants the resource again; sends
If-None-Match: "v1" - Server checks if the resource still matches
"v1"- If yes →
304 Not Modified(no body, cache is still valid) - If no →
200 OKwith new content and newETag: "v2"
- If yes →
The 304 response saves bandwidth — the client reuses its cached body without re-downloading it.
How optimistic concurrency control works
ETags also prevent lost updates in REST APIs — the "mid-air collision" problem:
- Client A fetches a document, receives
ETag: "v1" - Client B also fetches the same document, receives
ETag: "v1" - Client A updates it with
If-Match: "v1"→ succeeds, resource is now"v2" - Client B tries to update with
If-Match: "v1"→ server returns412 Precondition Failedbecause the ETag no longer matches
Without ETags, Client B would silently overwrite Client A's changes.
Generating ETags
Common approaches:
- Content hash: MD5, SHA1, or SHA256 of the response body. Changes when content changes. True strong ETag.
- Version/revision number: Database row version, git commit hash, or a sequential integer. Simple and cheap.
- Last-modified timestamp: Often used for files — the filesystem mtime. Weak because clock resolution means two rapid writes could share a timestamp.
- Combination:
"{content-hash}-{last-modified}"— robust.
How it interacts with other headers
ETag is used by clients in conditional request headers:
If-None-Match: "etag"— on GET: "give me the resource only if it has changed" (cache validation)If-Match: "etag"— on PUT/DELETE: "only apply this change if the resource still looks like this" (concurrency control)If-Range: "etag"— on range requests: "only serve the range if the resource hasn't changed since I last fetched it"
ETag and Last-Modified serve the same purpose (versioning a resource) but ETag is more precise. Last-Modified has 1-second granularity; multiple changes within the same second look identical. ETags have no such limitation. When both are present, ETags take precedence for validation.
Cache-Control: no-cache without ETags means the client always re-downloads on revalidation. With ETags, no-cache is cheap — 304 responses have no body.
Common mistakes and gotchas
Generating weak ETags when strong are needed. If you're going to support range requests for large file downloads, you need strong ETags (no W/ prefix). Range resumption depends on byte-level identity.
Not updating ETags when the resource changes. This sounds obvious but can be subtle — if your ETag is based on Last-Modified and your server has clock skew, rapid updates can produce the same ETag for different content.
Stripping ETags in reverse proxies. Some nginx/Apache configurations strip ETag headers from compressed responses. If you're compressing with gzip or br and your ETags disappear, check your proxy config.
Using ETags for sensitive data diffing. ETags can be used as a side-channel by attackers. If an attacker can infer whether two users got the same ETag for a private resource, they can determine whether two users have the same private data (e.g., same profile photo). Use Cache-Control: no-store for genuinely private content.
Real-world examples
Initial response:
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-cache
ETag: "33a64df551425fcc55e"
Content-Length: 512
{"id": 1, "name": "Sainesh", ...}
Conditional GET (cache validation):
GET /api/users/1 HTTP/1.1
If-None-Match: "33a64df551425fcc55e"
HTTP/1.1 304 Not Modified
ETag: "33a64df551425fcc55e"
Cache-Control: no-cache
Conditional PUT (concurrency control):
PUT /api/documents/42 HTTP/1.1
If-Match: "v7"
Content-Type: application/json
{"title": "Updated Title", ...}
HTTP/1.1 412 Precondition Failed
FAQ
Should I prefer ETag or Last-Modified?
ETags when you can. Last-Modified is easier to implement (just use the file mtime or DB updated_at), but ETags are more accurate — they're not limited to 1-second granularity and don't depend on clock synchronisation. Use Last-Modified as a fallback alongside ETag for maximum compatibility.
Does the ETag value format matter?
No — ETags are opaque to clients. The server generates them, the client stores them and sends them back verbatim. The value can be a hash, a UUID, a version number, anything — as long as it's quoted and changes when the content changes.
Do CDNs respect ETags?
Most do. CDNs use ETags for their own cache validation with the origin server (origin shield / CDN-to-origin revalidation). Whether a CDN forwards the ETag to end clients depends on the CDN configuration.
Can I use ETags on POST responses?
Yes, and it's good REST API design. A POST that creates a resource can return the new resource with an ETag — the client immediately has a valid ETag for future conditional updates without needing a separate GET.
Fun fact
The "Entity Tag" name comes from HTTP's original terminology where a response body was called an "entity." RFC 2616 (the 1999 HTTP/1.1 spec) used "entity" throughout — a word that quietly disappeared in RFC 7230-7235 (2014) in favour of "representation." The ETags themselves kept their name, even though "entity" is no longer used anywhere else in HTTP.