HTTP defines two ways for a server to identify a version of a resource. They are not interchangeable, and a site can easily end up in a state where they contradict each other.
Last-Modified
A timestamp indicating when the resource last changed. The client sends it back in If-Modified-Since, and the server compares dates.
Its weakness is resolution: HTTP dates have one-second granularity. Two changes within the same second are indistinguishable. It also requires the server to genuinely know when the content changed, which is harder than it sounds for a page assembled from many sources.
ETag
An opaque identifier for a specific version of a representation. The client sends it back in If-None-Match, and the server compares strings.
ETags have no resolution limit and can be derived directly from content, so they detect any change. The cost is that computing a content-derived ETag usually means generating the response before you can decide whether to send it.
Strong and weak validators
A strong ETag ("abc123") means byte-for-byte identical. A weak one (W/"abc123") means semantically equivalent — useful when compression or trivial formatting differences should not count as a change.
This matters in practice: when a server gzips a response, it may convert a strong ETag to a weak one, so the value the client sees differs from the value the application generated.
When the two disagree
Consider editing only a page’s meta description. The modification timestamp updates, so Last-Modified changes. But if that metadata does not appear in the rendered output, the body is byte-identical and the ETag does not change.
A client using If-None-Match now gets 304. A client using If-Modified-Since gets 200. Both are behaving correctly according to the specification. The site is simply making two different claims about whether it changed.
When both validators are present, If-None-Match takes precedence — RFC 9110 is explicit about this.