DevSwitch.net

ETags Explained: How HTTP Entity Tags Improve Cache Performance

A glowing tag icon linked to server and browser nodes illustrates HTTP ETag cache validation in a clean digital network scene.

An ETag (short for "entity tag") is a unique identifier that a web server attaches to a specific version of a file or resource, letting browsers ask "has this changed?" before downloading it again. When the answer is no, the server sends a tiny 304 Not Modified response instead of the full file, saving bandwidth and speeding up page loads. That simple back-and-forth is the heart of how ETags improve HTTP caching.

What are ETags

An ETag is a string of characters the server generates to represent one exact version of a resource. Think of it like a fingerprint: change a single byte in the file, and the ETag should change too. The value is opaque, meaning the browser never tries to interpret it. It only compares one ETag against another.

A typical response header looks like this:

HTTP/1.1 200 OK
Content-Type: text/css
ETag: "a3f5c9e8b1d2"
Cache-Control: max-age=3600

Servers usually build ETags from one of a few things:

  • A hash of the file contents (for example an MD5 or SHA digest).
  • The file's last-modified timestamp combined with its size and inode.
  • A version number the application assigns to the resource.

The exact recipe is up to the server. What matters is consistency: the same content should always yield the same ETag, and different content should yield a different one.

How ETags work step by step

ETags shine during cache validation, the process where a browser checks whether its cached copy is still good. Here is the full round trip:

  1. The browser requests a file for the first time. The server responds with 200 OK, the file body, and an ETag header like "a3f5c9e8b1d2".
  2. The browser stores both the file and its ETag in the cache.
  3. Later, when the cached copy expires, the browser sends the same request but adds an If-None-Match header carrying the stored ETag.
  4. The server compares the incoming ETag with the resource's current ETag.
  5. If they match, the server replies 304 Not Modified with no body. The browser reuses its cached file.
  6. If they differ, the server sends a fresh 200 OK with the new content and a new ETag.

That 304 response is often just a few hundred bytes, compared to a full file that might be tens or hundreds of kilobytes. Multiply that across every CSS file, image, and script on a page, and the savings add up fast. If you want a refresher on what these codes mean, our guide to HTTP status codes and what each number means breaks down the 2xx and 3xx families in detail.

Key point: A 304 response still counts as a network round trip. ETags don't eliminate the request, they eliminate the download. For zero-request caching you rely on Cache-Control: max-age instead, paired with cache busting so a changed file gets a new URL.

Strong vs weak ETags

The HTTP spec defines two flavors of entity tags, and the difference affects how strict the etag comparison is.

Type Syntax Meaning
Strong "a3f5c9e8b1d2" Byte-for-byte identical. Any change at all produces a new ETag.
Weak W/"a3f5c9e8b1d2" Semantically equivalent. Two versions can share a weak ETag even if bytes differ slightly.

A weak ETag carries the W/ prefix. It's useful when you want to treat two versions as "the same enough" (for example, a page that only changed a timestamp comment). Weak ETags cannot be used for range requests, because partial byte ranges need exact byte matching. Strong ETags can. The comparison rule also depends on the header. If-None-Match uses weak comparison, so W/"a3f5c9e8b1d2" matches "a3f5c9e8b1d2" and the server can still answer 304. If-Match and If-Range use strong comparison, so a weak ETag never matches, and an If-Match update sent with one fails with 412.

The full rules live in MDN's ETag documentation and the formal definition is in RFC 9110.

Conditional requests and headers

Conditional requests are requests that only complete an action if a condition is met. ETags power two of the most common ones:

  • If-None-Match — used for caching. "Send the file only if its ETag no longer matches this one." A match returns 304.
  • If-Match — used for safe updates. "Only apply my PUT or PATCH if the resource still has this ETag." A mismatch returns 412 Precondition Failed, preventing you from overwriting someone else's changes.

That second use case is a quiet superpower. It solves the "lost update" problem in APIs. If two people edit the same record, the second save fails instead of silently clobbering the first, a technique sometimes called optimistic concurrency control.

GET /styles.css HTTP/1.1
Host: example.com
If-None-Match: "a3f5c9e8b1d2"

# Server replies:
HTTP/1.1 304 Not Modified
ETag: "a3f5c9e8b1d2"

ETag vs Last-Modified

ETags aren't the only way to do cache revalidation. The older Last-Modified header pairs with If-Modified-Since to do a similar job based on timestamps.

Factor ETag Last-Modified
Precision Detects any change, even sub-second Limited to one-second resolution
Content awareness Can reflect actual content Only knows the timestamp
Edge case Handles files that change then revert Fooled if timestamp updates but content is identical
Overhead May require hashing Nearly free from filesystem metadata

When both headers are present, ETag takes priority: the browser sends If-None-Match and If-Modified-Since together, and the server must check If-None-Match and ignore If-Modified-Since. Many servers send both so clients can use whichever they support. For most modern setups, ETags give more reliable results.

Common ETag problems and fixes

ETags are simple in theory but trip people up in a few predictable ways:

  • Mismatched ETags across servers. Some servers (older Apache configs) build ETags from the file's inode number. On a load-balanced cluster, the same file has different inodes on each server, so ETags don't match and caching breaks. Removing the inode component (FileETag MTime Size on Apache) only helps if every server has the same file timestamps; content-based hashing fixes it for good.
  • ETags as unintended trackers. Because ETags are unique and persistent, they can be abused to identify returning visitors even after cookies are cleared. Privacy tools and audits flag this pattern, so avoid embedding user-specific data in ETag values.
  • Compression changing the tag. A gzipped response has different bytes from the plain file, so servers change its tag: Apache's mod_deflate adds a -gzip suffix, and nginx turns a strong ETag weak. If the server then fails to match that changed tag when the browser sends it back in If-None-Match, every revalidation returns a full 200 instead of a 304. Check with curl that a repeat request carrying the received ETag gets a 304; if it doesn't, fix how the server compares the changed tag rather than turning ETags off.
  • Disabling ETags unnecessarily. Some tutorials tell you to turn ETags off. That's only sensible if your Cache-Control and Last-Modified setup already covers you and cross-server mismatches are a problem. Otherwise you lose free revalidation.
Before disabling ETags, confirm your CDN or reverse proxy isn't already normalizing them. Many CDNs rewrite or strip ETags automatically, so the behavior you test locally may differ in production.
Illustration of HTTP status codes and cache validation responses

Decode the 304 responses ETags rely on

Since ETag cache validation lives and dies by the 304 Not Modified response, our full HTTP status code guide explains what it and the other common codes signal.

Read the status code guide →

They reduce bandwidth and load time by skipping downloads of unchanged files. When a resource hasn't changed, the server sends a tiny 304 response instead of the full file. The request still happens, but the transfer is far smaller, which speeds up repeat visits noticeably.

A strong ETag means the content is byte-for-byte identical, so any change creates a new tag. A weak ETag, marked with a W/ prefix, means two versions are semantically equivalent even if minor bytes differ. Strong ETags support range requests; weak ones do not.

Use ETags when you need precise change detection, since they catch sub-second edits and content that changes then reverts. Last-Modified is cheaper but limited to one-second timestamps. Many servers send both, and when a request carries both validators, the server checks the ETag and ignores the date.

This usually happens on load-balanced servers where ETags are built from file inode numbers, which differ per machine. On dynamic pages it also happens when the hashed output includes something that changes each time, such as a timestamp, CSRF token or nonce. Switch to content-based hashing, and leave per-request values out of what you hash.

Yes, and it's a known privacy concern. Because ETags are unique and persist in the cache, a server can assign a distinct value to each visitor and read it back even after cookies are deleted. Avoid putting user-specific data in ETag values to prevent this abuse.