The HTTP ETag header exists to save bandwidth on cache revalidation — but a unique per-visitor ETag turns your browser cache into a tracking ID.
Clearing cookies feels like a reset button, but it only empties one particular jar. The ETag header was designed for something mundane — telling a browser "you already have the latest version of this file, don't re-download it" — and that exact mechanism can be repurposed to hand each visitor a unique, silently re-confirmed identifier that lives in the browser cache, not the cookie store. A cache supercookie built this way survives a cookie clear because nothing about it was ever a cookie.
Key Takeaways
- An ETag is meant to be a version identifier for a cached file, but nothing stops a server from making that identifier unique per visitor instead of per file version — turning ordinary cache revalidation into a tracking round-trip.
- The technique was real, not theoretical: Samy Kamkar's evercookie project shipped a working ETag-based storage mechanism in 2010, and KISSmetrics was caught in 2011 using ETags (alongside Flash cookies) to respawn IDs that users had deliberately deleted, drawing a class-action lawsuit.
- ETags aren't the only cache-based vector — HSTS flags, favicon caching, and other pieces of browser state a site can set and later re-read have all been used the same way; ETags are simply the best-documented case.
- Modern browsers have closed most of the cross-site version of this hole. Since Chrome partitioned its HTTP cache by top-level site starting in Chrome 86, a cache entry (and its ETag) set on one site is no longer readable from another — Safari and Firefox have shipped their own versions of the same partitioning.
- Same-site ETag tracking still works, because partitioning only stops cross-site cache sharing — a single site can still assign your browser a unique ETag and read it back on every later visit to that same site, cookies or no cookies.
What an ETag Is Actually For
An ETag is an HTTP response header a server attaches to a resource — an image, a script, an API response — as a version identifier. MDN's own description of the header is unambiguous about the intent: it "lets caches be more efficient and save bandwidth, as a web server does not need to resend a full response if the content has not changed." How that value is generated isn't standardized; in practice it's typically a hash of the content, a hash of the last-modified timestamp, or a revision number.
The revalidation flow is simple:
- Your browser requests a resource. The server responds with the content and an
ETag: "abc123"header. - Your browser caches both the content and that ETag value.
- Next time you request the same resource, your browser sends
If-None-Match: "abc123"instead of blindly re-downloading. - If the server's current ETag still matches, it replies with a bare
304 Not Modified— no body, minimal bandwidth. If the content changed, it sends the new version with a new ETag.
That round trip — browser proves it already has something, server confirms whether that something is still current — is exactly the shape of a cookie check. The only difference is which header carries the identifier.
How an ETag Becomes a Tracking Identifier
Nothing in the HTTP specification requires an ETag to represent a content version. A server is free to generate a unique, per-visitor ETag the first time you load a tracking pixel or script, store that value against your session server-side, and hand it back to you as ETag: "visitor-8f2c91a4". Your browser, having no way to distinguish a legitimate cache-freshness token from a tracking token, dutifully caches it and echoes it back in If-None-Match on every subsequent request for that resource — silently re-identifying you to the server on each page load, with no cookie, no localStorage write, and no JavaScript required at all.
This is precisely the mechanism Samy Kamkar's evercookie project demonstrated in 2010: alongside HTML5 storage, Flash Local Shared Objects, and a half-dozen other mechanisms, it included a server-backed ETag component that stores an identifier in the HTTP ETag and reads it back on the next request, specifically so the ID could be recovered even after cookies were cleared. The project's stated goal was to show just how many independent, non-cookie storage vectors a determined tracker could stitch together — ETag caching turned out to be one of the more durable ones, because deleting a cookie has never emptied the browser's HTTP cache.
The technique moved from proof-of-concept to production quickly: in 2011, researchers and journalists documented KISSmetrics — an analytics vendor used by sites including Hulu and Spotify — using ETags together with Flash cookies to respawn tracking identifiers that users had explicitly deleted. The resulting privacy backlash led to a class-action settlement, and it remains the clearest real-world case of ETag tracking being deployed at scale rather than just demonstrated as a proof of concept.
Cache-Based Tracking Isn't Only About ETags
ETags get the most attention because they're the cleanest example, but they're one member of a broader family of cache-based supercookies — identifiers smuggled into any piece of browser state a site can both set and later read back, outside the cookie jar:
| Vector | What gets set | What gets read back |
|---|---|---|
| ETag tracking | A unique ETag on a cached resource | If-None-Match on the next request |
| HSTS supercookie | Which of a set of subdomains get HSTS-pinned (each bit encodes part of an ID) | Whether the browser upgrades each subdomain to HTTPS without a round trip |
| Favicon caching | A uniquely-URLed favicon, cached separately from other storage | Whether the browser re-requests it or serves the cached copy |
| Cache-Control timing | Presence/absence of a resource in cache | Load-time difference between a cache hit and a fresh fetch |
All four share the same structural trick as bounce tracking and CNAME cloaking: they exploit a mechanism that was never designed with tracking in mind, so cookie-specific defenses — blocking third-party cookies, clearing the cookie jar — simply don't apply to them.
Why It Survives Cookie Clears and Incognito
A cookie clear only empties the cookie store. The HTTP cache is a separate subsystem with its own storage and its own "clear" checkbox in most browsers' privacy settings — one users frequently skip, since "clear browsing data" dialogs list cache and cookies as independent options. An ETag written to the cache stays there, unaffected, until the cache itself is cleared or the entry naturally expires.
Private/incognito windows don't inherently help either: a private session still uses an HTTP cache for the duration of the window, so an ETag set earlier in that same private session behaves identically to a normal one — it's discarded only when the window closes, at the same time as everything else. Our guide to how sites detect Incognito mode covers a related but distinct problem — sites noticing you're in a private window via storage-quota quirks — which is a separate question from whether cache-based identifiers persist within one.
Browser Mitigations: Cache Partitioning Closed the Cross-Site Hole
The biggest structural fix wasn't aimed at ETags specifically — it targeted the HTTP cache itself. Starting with Chrome 86, Chrome partitioned its HTTP cache by top-level site: instead of keying a cached resource by URL alone, Chrome keys it by URL plus the top-level site (and, in later refinements, the frame site) that requested it. The Chrome team's own writeup frames the motivation explicitly around this kind of abuse — cache presence had been usable to infer browsing history and to plant cross-site tracking identifiers, and partitioning removes the shared cache that made either possible. Safari and Firefox have each shipped comparable cache-partitioning schemes since.
The practical effect: a tracking script embedded on site-a.com can no longer set an ETag that gets read back when the same script loads on site-b.com, because each site now gets its own isolated slice of the cache. Cross-site ETag tracking — the version that let a single ad-tech vendor correlate you across every site it was embedded on — is largely closed in current browsers.
What partitioning does not fix is same-site tracking: a single site you visit repeatedly can still assign your browser a unique ETag on first load and read it back every time you return, entirely within its own partition, with no cookie involved. Cache partitioning was built to stop cross-site correlation, not to stop a site from recognizing its own repeat visitors by a non-cookie means.
How to Detect and Mitigate ETag Tracking
There's no single toggle that neutralizes this the way "block third-party cookies" neutralizes classic tracking, which is exactly why Privacy Badger's own tracker (EFForg/privacybadger#2136) on detecting ETag supercookies is still open: legitimate servers set ETags constantly for ordinary caching, so distinguishing a version identifier from a tracking identifier from the outside is a genuinely hard heuristic problem, not a simple pattern match.
A few things actually help:
- Clear the HTTP cache, not just cookies. Most browsers' "clear browsing data" dialog lists "Cached images and files" as a separate checkbox from "Cookies and other site data" — check both.
- Use private/incognito mode for sessions you don't want linked, since its cache (like its cookies) is discarded when the window closes — it just won't stop tracking within that one session.
- Disable caching entirely for sensitive contexts via browser DevTools' "Disable cache" option, or a privacy extension that strips or randomizes
ETag/If-None-Matchhandling — at the cost of the bandwidth savings ETags exist to provide. - Rely on cache partitioning doing its job for the cross-site case — it's shipped by default in every major browser at this point, so the highest-value fix already happened without any action on your side.
None of this requires reading JavaScript on the page, because the whole point of the technique is that it never touches a storage API a script could inspect — it lives entirely in HTTP headers exchanged before any page content loads.
The ETag exchange itself happens below the layer any page script can read, but a cache identifier is rarely deployed alone — trackers stitch it together with everything else your browser exposes. BrowserInsight's fingerprint check shows the canvas, WebGL, storage, and other client-side signals a tracker would combine with a cache-based ID, all measured in your browser and never sent anywhere for analysis.
Frequently Asked Questions
Does an incognito/private window stop ETag tracking?
Only between sessions, not within one. A private window's cache is discarded when you close it, so an ETag set during that session can't be read back after you reopen the browser — but for as long as the private window stays open, ETag-based re-identification works exactly the same as in a normal window.
Does blocking third-party cookies stop ETag supercookies?
No, and that's the entire reason the technique got attention. Third-party cookie blocking inspects the Cookie header specifically; it has no visibility into ETag or If-None-Match, so a cache-based identifier sails through a cookie blocker untouched. What actually stops the cross-site version is HTTP cache partitioning, a separate browser mechanism.
Can a VPN or clearing cookies alone stop this?
No to both. A VPN changes your IP address, not your browser's cache contents. Clearing cookies empties the cookie store but leaves the HTTP cache — and any ETag sitting in it — completely untouched, which is precisely the gap this technique was built to exploit.
Is ETag tracking still used today?
Cross-site ETag tracking has become far less effective since Chrome, Safari, and Firefox all shipped HTTP cache partitioning, which prevents a cached entry (and its ETag) set on one site from being read on another. Same-site ETag tracking — a single site re-identifying its own repeat visitors — remains technically possible and isn't addressed by cache partitioning at all.
Conclusion
ETag tracking is a reminder that "clear your cookies" was always advice about one specific storage mechanism, not a guarantee against being recognized. The HTTP cache exists to make the web faster, and any mechanism a server can both write and read back — an ETag, an HSTS flag, a favicon URL — can be repurposed into an identifier the moment uniqueness gets attached to it instead of a genuine content version. Cache partitioning closed the most damaging cross-site version of the problem; the same-site case is a smaller, quieter one that's still worth knowing about the next time a cookie clear doesn't seem to have reset anything.
Recommended Reading:


