ETag: version identifiers for conditional requests
An ETag is an opaque version identifier for one representation of a resource. Clients send it back so the server can answer 304 Not Modified instead of resending unchanged bytes.
At a glance
- Direction
- Response header. Clients echo the value in If-None-Match or If-Match.
- Defined in
- RFC 9110
- When absent
- Revalidation falls back to Last-Modified, which has one second resolution and cannot detect a same-second edit.
- Format
- A quoted string, optionally prefixed with W/ for a weak tag: W/"abc123"
- Strong comparison
- Used by If-Match and by range requests. Byte for byte identity required.
- Weak comparison
- Used by If-None-Match. Semantically equivalent representations may share a tag.
- Changes with
- The representation, so compression or a different language usually needs a different tag.
In detail
The value is opaque: clients never parse it, they only compare it. A server can build it from a content hash, a database row version, or a modification timestamp plus a size, and the choice is invisible to the client. A hash is the most reliable because it cannot say unchanged when the bytes have in fact changed.
A weak tag, written with the W/ prefix, says two representations are equivalent for caching even if the bytes differ. That is what you want when a page embeds a timestamp or a rotating advertisement that nobody needs to refetch. Strong tags promise byte identity, which is what range requests need before they can stitch a partial download onto a stored copy.
The tag drives two very different flows. Sent back in If-None-Match on a GET, it produces 304 Not Modified and saves the body. Sent back in If-Match on a PUT or PATCH, it produces 412 Precondition Failed when somebody else has already written, which is the standard way to stop two editors silently overwriting each other.
A representation includes its encoding, so a gzipped and an uncompressed copy are different representations and should not share a strong tag. Servers that compress at the edge often weaken the tag automatically for exactly this reason, which is why an ETag can gain a W/ prefix in production that it never had locally.
Examples
HTTP/1.1 200 OK
ETag: "9c2f7a1e4b"
Cache-Control: no-cacheGET /report HTTP/1.1
If-None-Match: "9c2f7a1e4b"
HTTP/1.1 304 Not Modified
ETag: "9c2f7a1e4b"ETag: W/"v42-en"Weak tags cannot be used for range requests, so do not use them on large downloadable files.
Things that catch people out
- The quotation marks are part of the syntax. ETag: abc123 without them is malformed, and some caches will ignore it.
- Generating the tag from a file inode or modification time breaks the moment you run more than one server, because the same content produces different tags on each machine.
- A 304 response must repeat the ETag. Omitting it makes some caches discard the stored copy and refetch the whole body next time.
Try it: HTTP Headers Checker
Check a live URL or paste headers you already have. It grades six security headers from A+ to F and lists every other header the response returned.
HTTP Headers Checker
LOCAL · Browser-OnlyView HTTP response headers for any URL
Enter any website URL to fetch and analyze its HTTP response headers. We'll automatically add https:// if needed.
Why Check Security Headers?
Security headers protect your website against common attacks like XSS, clickjacking, and MIME sniffing. They're set by your web server and cost nothing to implement. Missing security headers are one of the most common findings in security audits.
Related HTTP headers
All http headers →If-None-Match: asking whether anything changed
How If-None-Match produces a 304, why it uses weak comparison, how it overrides If-Modified-Since, and its second job preventing duplicate creates.
If-Match: safe updates and the lost update problem
How If-Match turns a PUT or PATCH into a conditional write, why it uses strong comparison, and how 412 Precondition Failed prevents silent overwrites.
Last-Modified: timestamps as a weak validator
The exact date format Last-Modified requires, why one second resolution makes it a weak validator, and how caches use it to guess freshness.
Cache-Control: directives for browsers and shared caches
Every Cache-Control directive that matters, the difference between no-cache and no-store, and how to cache immutable assets forever without breaking deploys.
Expires: the older way to set a freshness lifetime
What Expires does, why Cache-Control max-age overrides it, what an invalid date such as 0 means to a cache, and whether it is still worth sending.
Age: how long a cached response has been sitting there
What the Age header counts, how it combines with max-age to give remaining freshness, and how to use it to tell a cache hit from a miss at the edge.
Tools for this
- HTTP Headers Checker Free online HTTP headers checker - view HTTP response headers for any URL
- HTTP Request Builder Free online HTTP request builder - build and test HTTP requests with custom headers and body