A cache can ask whether its stored copy is still usable without always downloading the body again. It sends a validator; a matching conditional GET can receive 304 Not Modified, while a changed representation normally needs a new 200 response. Validation saves bytes on a hit, not on every request.
Validator type and strength are different questions
An ETag is an opaque, quoted value. It can be strong ("release-a") or explicitly weak (W/"release-a"). Strong comparison requires both tags to be strong and equal; weak comparison compares the opaque values regardless of the W/ marker. Weak tags can describe semantic equivalence even when bytes differ.
Last-Modified is a timestamp with one-second HTTP-date resolution. It is usually weak unless the conditions in RFC 9110 section 8.8.2.2 establish it as strong. Do not classify every ETag as strong or every timestamp as unconditionally weak.
The earlier site observation recorded:
etag: "dkx84jwth7nkc9e"
last-modified: Mon, 24 Aug 2026 13:55:23 GMT
These are historical values; the original response log is not retained here and the live result was not rechecked in this review. Caddy’s file server normally sets both fields, subject to file metadata and configuration.
Conditional GET uses weak comparison
Send the ETag exactly as received in If-None-Match, including its quotes and any W/. The original note reported HTTP 304 for the value above but printed an unquoted request value. That request text was not valid entity-tag syntax and should not be copied as a working probe. Obtain a current validator before testing a live endpoint.
This socket-free exercise illustrates the comparison for individual, already-parsed tags; it is not a complete HTTP header parser:
def weak_value(tag):
return tag[2:] if tag.startswith('W/') else tag
def strong_equal(a, b):
return not a.startswith('W/') and not b.startswith('W/') and a == b
stored = 'W/"release-a"'
current = '"release-a"'
assert weak_value(stored) == weak_value(current)
assert not strong_equal(stored, current)
assert weak_value(stored) != weak_value('"release-b"')
print("weak match: True; strong match: False; changed tag: no match")
For an otherwise successful GET or HEAD, a matching If-None-Match produces a body-free 304. The same false precondition on another method normally produces 412 Precondition Failed. If both If-None-Match and If-Modified-Since are present, the ETag condition takes precedence. Byte-range validation with If-Range cannot use a weak ETag.
Republish without preserving a misleading validator
An ETag is not necessarily a content hash. Caddy v2.10.2’s default file tag is derived from modification time and size; replacing bytes while preserving both can preserve the tag. Inspect the deployed version and any configured overrides, then make the release process update the validator’s inputs or use content-based versioning.
Keep byte checksums in the release manifest and test both unchanged and changed responses. A checksum does not automatically become the server’s ETag. A 304 also needs the applicable cache metadata so a cache can update its stored response; it is not merely an empty success status.
Sources
- RFC 9110 — HTTP Semantics, sections 8.8 and 13 on validator strength and preconditions, checked 2026-10-15.
- RFC 9111 — HTTP Caching, section 4.3 on validation and updating stored responses, checked 2026-10-15.
- MDN: HTTP conditional requests, conditional-request overview, checked 2026-10-15.
- Caddy v2.10.2 file-server source,
calculateEtaginputs and default response fields, checked 2026-10-15.