The Question That Saves Nothing: ETag, Last-Modified and the Missing 304

Management Summary
A browser can keep a copy of website content to avoid downloading the same material again. When that copy needs checking, a version label or modification date lets the server confirm whether it is still current. This can save the repeated transfer of unchanged content, although asking the server still takes time. The article explains the difference between reusing a saved copy, checking it and receiving a new one. Understanding these cases helps judge whether a website’s caching actually reduces unnecessary work.
Contents
A cached response is fresh while the cache rules permit reuse without asking the server. Such a response can avoid a network request. Once checking becomes necessary, a validator can avoid transferring the body again: a matching conditional GET can receive 304 Not Modified. That still costs a request and processing time. A cache without validators can therefore remain useful while a stored response is reusable.
A historical run from 5 September 2026 covered twenty starting URLs. Eleven recorded responses contained neither an ETag nor Last-Modified. This describes those responses, not an inability of eleven entire websites to support conditional requests.

Two validators, different guarantees
Last-Modified describes the last modification time with one-second precision. The corresponding request field is If-Modified-Since. Multiple changes within a second can exceed that precision.
ETag is a version label whose contents the client need not interpret. The label is returned in If-None-Match. Its reliability depends on generation: a content hash or another suitable version scheme can distinguish changes that timestamps miss. An ETag is not automatically a content hash.
A strong ETag identifies identical representation data. A weak ETag, such as W/"abc", permits equivalent data without byte identity. Dynamic generation does not automatically require weak tags: the actual comparison guarantee matters.
What the saved sample contains
The archived results contain five ETags, including three weak ones, and five Last-Modified fields. One response has both; eleven have neither. Six responses specify no-store, but only four of those lack both validators. Seven responses therefore lack validators without also specifying no-store. This alone does not establish cacheability: status, other directives and the cache context also matter.
Two results carry an HTTP-error flag, including one of the two responses without Cache-Control. The saved summary does not preserve their exact status codes. The sample must therefore not be presented as twenty successful front-page responses or as a market statistic.
The analyzer assigned six storage-prohibited, four immediate-refetch, three always-revalidate, four fresh-then-revalidate, one fresh-then-refetch and two heuristic labels. These are classifications of header observations, not measured browser histories. For example, the 54-second value was a max-age directive, not proof that every browser kept the response for exactly 54 seconds.
Freshness is separate from validation
no-store prohibits HTTP-cache storage; no-cache requires successful validation before reuse. max-age=0 makes a response immediately stale. Private and shared caches can have different rules, particularly with private or s-maxage.
Without an explicit reuse period, a cache may estimate one under suitable conditions. This is called heuristic caching. Ten percent of the interval since Last-Modified is an example heuristic, not a mandatory lifetime. Neither of the two sampled responses without Cache-Control contained Last-Modified, so the archive does not demonstrate that calculation for those responses. Browser back/forward mechanisms also cannot be inferred from this HTTP test.
Compression and the ETag trap
Apache documents that its default AddSuffix behavior in the compression modules can prevent 304 responses for compressed content. This makes a same-variant conditional test useful. A repeated 200 alone is insufficient evidence: the selected representation may genuinely have changed.
DeflateAlterETag NoChange and BrotliAlterETag NoChange preserve the original tag, but Apache explicitly documents the resulting variant-identity limitation. A shared strong tag for compressed and uncompressed bytes is not made correct by Vary: Accept-Encoding. Vary controls cache selection; strong validators concern representation identity.
A suitable solution depends on the server and cache pipeline: correct distinct strong tags, properly handled weak tags for equivalent representations, or removal of an unsuitable ETag with the resulting validation trade-off. Range requests and conditional responses need verification. No universal production directive follows from a single 200 result. The earlier article’s claim that NoChange was unconditionally safe has therefore been removed.

A reproducible conditional GET
The Bash example needs curl 7.68.0 or later. It keeps the URL and encoding preference unchanged, preserves the received ETag, and stops if the initial response is not 200 or supplies no tag. The URL is a placeholder; redirects require selecting and testing the intended final URL separately.
#!/usr/bin/env bash
set -euo pipefail
url='https://example.com/style.css'
tag_file=$(mktemp)
trap 'rm -f "$tag_file"' EXIT
status=$(curl --silent --show-error --max-time 20 \
-H 'Accept-Encoding: gzip' --etag-save "$tag_file" \
--output /dev/null --write-out '%{http_code}' "$url")
[[ "$status" == 200 ]] || { printf 'Initial HTTP status: %s\n' "$status" >&2; exit 1; }
[[ -s "$tag_file" ]] || { printf 'No ETag received\n' >&2; exit 1; }
curl --silent --show-error --max-time 20 \
-H 'Accept-Encoding: gzip' --etag-compare "$tag_file" \
--output /dev/null --dump-header - --write-out '\nHTTP %{http_code}\n' "$url"The displayed response headers make the second ETag and encoding visible. A 304 has no response body; a 200 needs examination of variant, modification and server behavior. Network failures and other status codes remain separate outcomes. Keeping Accept-Encoding identical controls one input, but does not prove that the server chose the same encoding or content.
The Cache-Control and Revalidation Analyzer sends the received ETag and date in separate tests. It does not test their combined precedence or a full browser cache. Its byte counter measures decoded content, capped at 256 KiB, rather than complete compressed traffic. Its warnings also flag changed ETags returning 200; that can be valid behavior. The heuristic duration and classification require interpretation rather than automatic acceptance.
Questions and answers
What happens when both conditions are sent?
If-None-Match takes precedence; If-Modified-Since is ignored when that field is present. A matching date therefore does not override a nonmatching ETag. Actual client requests need inspection; every browser does not necessarily send both on every visit.
Can a weak ETag produce 304?
Yes. If-None-Match uses weak comparison, so W/"abc" and "abc" match. That does not make a weak tag suitable for an entity-tag If-Range test or an If-Match strong comparison.
Is every 200 evidence of a defect?
No. A changed representation normally needs a new body. Diagnosis compares the request, selected variant and returned validator. Browser network tools, server logs and automated tests can expose the issue; manual curl is one method.
Does 304 avoid a network round trip?
No. A network validation still needs a response, possibly from an intermediary cache rather than the origin. A reusable fresh entry can avoid that request entirely. Versioned static assets can use longer freshness when the URL changes with every content update.


