ETag
Cache revalidation and concurrency control both need a reliable way to identify resource versions. The HTTP ETag response header provides an opaque identifier for a specific version of a resource, serving both purposes through conditional requests.
Usage
The ETag header assigns a unique tag to each version of a resource. When the resource changes, the ETag value changes. Clients store the ETag from a cached response and send the ETag back in the If-None-Match request header on subsequent requests. If the ETag still matches, the server returns 304 Not Modified with no body, and the client reuses its cached copy. If the ETag no longer matches, the server sends the full updated response.
ETag-based validation is more reliable than date-based validation with Last-Modified. Strong entity tags are opaque strings tied to the exact content of a representation, while modification timestamps have one-second granularity and are subject to clock skew. When both ETag and Last-Modified are present, the ETag takes priority during revalidation as required by the HTTP standard.
ETags also enable concurrency control on unsafe methods. A PUT request with If-Match ensures the update applies only to the expected version. If the resource has changed since the client last fetched the ETag, the server returns 412 Precondition Failed, preventing the lost update problem.
The ETag value is generated by the server. Common
strategies include content hashing, version counters,
and combining the file modification time with size. The
value is enclosed in double quotes and optionally
prefixed with W/ for weak comparison.
Note
Google recommends using ETag over Last-Modified to indicate caching preference because ETag avoids date formatting issues. When both ETag and Last-Modified are present, Google's crawlers use the ETag value as required by the HTTP standard. Setting both headers is still recommended because other applications such as CMSes also rely on these headers. Individual Google crawlers and fetchers make use of caching depending on the needs of their associated product. Googlebot supports caching when re-crawling URLs for Google Search.
Values
Strong ETag
A strong ETag guarantees byte-for-byte identity between two representations of a resource. Two resources with the same strong ETag are interchangeable at the byte level. Strong ETags are suitable for range requests and all comparison methods.
ETag: "abc123"
Weak ETag
A weak ETag, prefixed with W/, indicates semantic
equivalence rather than byte-level identity. Two
resources with the same weak ETag are considered
equivalent in meaning but not necessarily identical in
content. Weak ETags are easier to generate because they
tolerate minor differences like whitespace changes or
timestamp updates in the body. Weak ETags are not valid
for byte-range requests.
ETag: W/"v2.6"
Example
A server returns an ETag with the response. The client stores the tag for future revalidation.
HTTP/1.1 200 OK
ETag: "33a64df5"
Content-Type: text/html
Cache-Control: max-age=3600
The client revalidates using If-None-Match. The server checks the tag against the current resource version.
GET /page HTTP/1.1
If-None-Match: "33a64df5"
The resource has not changed. The server returns 304 with no body and the client reuses its cache.
HTTP/1.1 304 Not Modified
ETag: "33a64df5"
Cache-Control: max-age=3600
A PUT request using If-Match for optimistic concurrency. The update proceeds only if the resource still matches the ETag the client holds.
PUT /api/document/42 HTTP/1.1
If-Match: "33a64df5"
Content-Type: application/json
A weak ETag on a resource where minor body variations
(like embedded timestamps) are acceptable. The W/
prefix signals weak comparison semantics.
ETag: W/"2024-11-22-v3"
Troubleshooting
ETag-related issues surface as failed revalidation, unexpected full downloads, or concurrency conflicts across distributed infrastructure.
ETag mismatch across load-balanced servers. Different servers behind a load balancer generate different ETags for the same resource when the generation algorithm includes inode numbers, timestamps, or server-specific data. A client cached the response from server A, but revalidates against server B, which returns a different ETag and forces a full download. In Apache, the default ETag includes inode, size, and mtime. Remove the inode component:
FileETag MTime SizeIn nginx, ETags derive from last-modified time and content length, producing consistent values across servers sharing the same file content. Ensure file deployment timestamps match across all nodes.
Weak ETag causes unexpected full response instead of 304. Weak ETags (
W/"...") signal semantic equivalence but do not support byte-range requests. An If-None-Match request with a weak ETag still works for cache revalidation. If the server returns a full 200 response instead of 304, the server-side comparison logic may be incorrect. Verify the server compares weak ETags using the weak comparison function, which ignores theW/prefix during matching.If-None-Match not sent by the client. The browser sends If-None-Match only when a cached response includes an ETag header. Missing ETags in the initial response prevent all subsequent revalidation. Check the origin response with
curl -I https://example.re/resourceand confirm the ETag header is present. Static file servers like nginx and Apache generate ETags automatically. Application servers and API frameworks often require explicit ETag generation in code.CDN strips or modifies ETags. Some CDNs alter ETag values during processing. Cloudflare converts strong ETags to weak ETags when applying transformations like Brotli compression or image optimization. AWS CloudFront passes ETags through unmodified from the origin. Check the ETag at the origin (
curl -I https://origin.example.re) and compare with the CDN edge (curl -I https://cdn.example.re). When the CDN modifies ETags, revalidation through the CDN still works because the CDN stores the mapping. Direct-to-origin revalidation with a CDN-modified ETag fails.Apache and nginx generate ETags differently. Apache defaults to inode-mtime-size, producing ETags like
"1a2b3c-4d5e-6f7a8b9c". Nginx uses mtime-content-length in hex, producing ETags like"5f4dcc3b-d8". Migrating between servers invalidates all cached ETags, triggering full downloads for every client. Plan for a cache warming period after server migrations. For API responses, generate ETags from content hashes (MD5 or SHA-256) to produce server-independent values.
Note
Apache's default ETag generation includes the file
inode number (FileETag INode MTime Size). Inode
numbers leak server filesystem information, enabling
fingerprinting and cross-server correlation. OWASP
flags inode-based ETags as an information disclosure
risk. Remove inode from the calculation with
FileETag MTime Size in Apache configuration.
Note
For SEO and caching assistance, contact ex-Google SEO consultants Search Brothers.
See also
- RFC 9110: HTTP Semantics, Section 8.8.3
- RFC 9111: HTTP Caching
- Google: HTTP caching for crawlers
- Google Search Blog: Crawling December, HTTP caching
- If-None-Match
- If-Match
- Last-Modified
- If-Modified-Since
- 304
- 412
- Conditional-Requests
- Caching
- HTTP headers