An ETag (entity tag) is an HTTP response header that carries an opaque identifier for one specific version of a resource. A client stores it and sends it back in an If-None-Match request header; if the tag still matches, the server answers 304 Not Modified with headers only — typically 150–400 bytes instead of the full body. The ETag is HTTP's most precise cache validation mechanism, specified in RFC 9110.
The origin computes a tag when it serves the resource and returns it as ETag with a quoted value, for example a content hash or a version counter. The client caches body plus tag. Once the freshness lifetime from Cache-Control expires, the cached copy becomes stale but is not discarded.
On the next request the client revalidates: it sends If-None-Match with the stored tag. The server compares it against the tag of the currently selected representation. Match means 304 Not Modified plus updated freshness headers; no match means 200 with a new body and a new tag. Timing matters — revalidation still costs a full round trip, so ETags save bandwidth, not latency. Pair them with stale-while-revalidate if you want the round trip off the critical path.
HTTP ETag validation (RFC 9110) turns a full-body transfer into a 304 Not Modified response of roughly 150–400 bytes in 2026 measurements. For a 300 KB asset revalidated 10 million times per day with a 90% match rate, that moves origin egress from about 3,000 GB to about 303 GB — a 90% reduction. The figures are an illustrative calculation, not a vendor benchmark, but the ratio holds for any asset large enough that headers are noise.
A strong validator guarantees byte-for-byte identity. A weak validator, prefixed with W/, only claims semantic equivalence: the resource is "close enough" that a cached copy is still usable. That distinction is not cosmetic.
| Validator | If-None-Match (GET) | If-Range / byte-range resume | If-Match (concurrency) | Sub-second change detection |
|---|---|---|---|---|
| Strong ETag | Yes | Yes | Yes | Yes |
| Weak ETag (W/ prefix) | Yes | No | No | Yes |
| Last-Modified date | Via If-Modified-Since | Only when strongly validating | No | No — one-second granularity |
If you serve large installers, video segments, or anything resumable, you need strong ETags: byte-range resumption and optimistic concurrency both require strong comparison.
Three layers hold a copy of the same tag, and each uses it differently. The browser holds it per URL and per Vary key. The CDN edge holds it against the cached object and, on TTL expiry, revalidates upstream with If-None-Match rather than refetching — a 304 from origin resets the object's freshness in place. The origin is the only layer that can authoritatively generate it.
Two failure modes dominate at scale. First, per-server tags: nginx derives its default ETag from file modification time and content length, so an unsynchronised fleet hands out different tags for identical bytes, and every revalidation misses. Second, compression: nginx downgrades a strong ETag to weak when it gzips on the fly, which silently kills range resumption. Generate content-hash ETags at build time and ship them with the artifact. This is where flexible edge configuration earns its keep — BlazingCDN supports per-path cache rules, so you can normalise or bypass validators on routes where origin-generated tags are unreliable, using the same per-path cache validation controls you use for TTL overrides.
ETag vs. Last-Modified: both are validators, but Last-Modified has one-second resolution and cannot distinguish two edits inside the same second. Send both; clients prefer the ETag when present, and Last-Modified stays useful for heuristic freshness when no Cache-Control max-age is set.
ETag vs. Cache-Control: Cache-Control decides how long a copy may be used without asking. The ETag decides what happens after that window closes. Cache-Control controls freshness; the ETag controls validation. A resource with no-cache and a strong ETag is fetched on every request but usually answered with 304.
ETag vs. cache-busting fingerprints in the URL: a hashed filename makes the URL immutable, so the client never revalidates at all. That is strictly cheaper than any ETag exchange. Use fingerprinted URLs plus immutable for build artifacts, and reserve ETags for resources whose URL must stay stable — HTML documents, API responses, avatars.
Response (first request)
HTTP/2 200
ETag: "9c1e8f4a2b7d"
Cache-Control: public, max-age=60
Last-Modified: Tue, 14 Jul 2026 09:12:44 GMT
Vary: Accept-Encoding
Content-Length: 307200
Request (after 60 seconds)
If-None-Match: "9c1e8f4a2b7d"
Response
HTTP/2 304
ETag: "9c1e8f4a2b7d"
Cache-Control: public, max-age=60
Age: 0
On nginx, the two directives that matter are etag on; and, for build artifacts, add_header Cache-Control "public, max-age=31536000, immutable";. Note the Vary on Accept-Encoding: the tag identifies the selected representation, so brotli and gzip copies of the same file must not share one strong ETag.
"ETags make the site faster." They cut transferred bytes, not round trips. A 304 still costs one RTT. If the p95 RTT to origin is 90 ms, revalidating 40 assets serially is slower than serving them from an immutable cache.
"Disable ETags, they leak inode numbers." That was Apache's default format two decades ago. Modern defaults do not expose inodes. Disabling validators just forces full refetches.
"A 304 means the CDN was bypassed." A 304 from an edge is a hit on the validation path. Track it separately from 200-hits, or your reported hit ratio will be wrong.
An ETag is an HTTP response header containing an opaque token that identifies one version of a resource. Clients return it in If-None-Match, and the server replies 304 Not Modified when the token still matches, sending headers only. RFC 9110 defines the syntax and the comparison rules for strong and weak tags.
A strong ETag guarantees the bytes are identical; a weak ETag, written with a W/ prefix, only guarantees the representation is semantically equivalent. Both work for If-None-Match on a GET. Only strong tags are valid for If-Range byte-range resumption and for If-Match optimistic concurrency, which use strong comparison.
No, but replace the default. The default nginx ETag is derived from file modification time and size, which diverges across origin servers and breaks validation behind a load balancer. Emit a content-hash ETag from your build pipeline instead, and keep Last-Modified as a fallback validator for older intermediaries.
They reduce origin egress, not client-side delivery volume. When an edge cache revalidates an expired object and receives 304 Not Modified, the object is refreshed in place without re-fetching the body. For frequently revalidated assets, that removes most origin-to-edge transfer while leaving edge-to-client traffic unchanged.
Yes, and it is the recommended configuration. Clients that support both send If-None-Match and If-Modified-Since; per RFC 9110, a server that has an ETag should evaluate If-None-Match first and may ignore the date. Last-Modified remains useful for heuristic freshness calculations when no explicit max-age is present.
Run one measurement this week: split your edge and origin logs into 200-hits, 304-validations, and 200-misses, then compute what share of origin requests return 304. If that share is above roughly 20% and the affected assets are versioned build artifacts, you are paying a round trip per asset for nothing — move those paths to fingerprinted URLs with immutable and delete the revalidation entirely. If your 304 rate is near zero on stable content, check whether your origin fleet is emitting divergent ETags. Which pattern does your traffic show?