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.

The cache sends the exact weak ETag W/"release-a" in If-None-Match. Weak comparison matches the origin tag "release-a", giving a body-free 304; the cache updates metadata and reuses stored body A. Alternatively, origin tag "release-b" produces 200 and new body B for GET. HEAD uses the same status test but returns no body. Weak matching implies neither byte identity nor freshness.
Illustrative tags; otherwise successful requests. GET/HEAD use weak comparison. HEAD has no body. Matching is neither byte identity nor freshness. Other methods normally return 412; If-Range cannot use weak ETags.

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