HTTP Cache Freshness Calculator

Paste the headers of one HTTP response. The page works out, for a private (browser) cache, a shared (proxy) cache and a CDN cache side by side, whether the response may be stored, its freshness lifetime and which rule produced it, its current age with every intermediate value, and whether it is fresh or stale. Three caches can give three different answers to the same response, and each one is following its own rule. Every verdict is labelled per RFC 9111 or per RFC 9213; the page models the RFCs, not any particular product.

1. The response and the clock

Load an example, or paste from curl -I, a browser's raw header view, or similar. Everything is evaluated in this page; nothing is sent anywhere.

Times take an HTTP-date or an ISO 8601 UTC time such as 2026-10-01T12:15:00Z. Everything is computed and shown in UTC; your local time zone is never used. With no Date header and empty fields, all three times default to this device's clock when you press Calculate.

Request

Only GET and HEAD are treated as cacheable here.

corrected_initial_age
Results also update as you type.

2. Three caches, side by side

3. The rules applied

  • R1 Storage, RFC 9111 3: no-store means not stored; a shared cache does not store an unqualified private response ("The unqualified private response directive indicates that a shared cache MUST NOT store the response"); a shared cache does not store a response to a request with Authorization unless must-revalidate, public or s-maxage allows it (3.5); and the response must contain at least one of public, private (private cache only), Expires, max-age, s-maxage (shared cache only) or a heuristically cacheable status.
  • R2 Heuristically cacheable statuses, RFC 9110 15.1: 200, 203, 204, 206, 300, 301, 308, 404, 405, 410, 414 and 501.
  • R3 Freshness lifetime, RFC 9111 4.2.1, first match: "If the cache is shared and the s-maxage response directive (Section 5.2.2.10) is present, use its value, or If the max-age response directive (Section 5.2.2.1) is present, use its value, or If the Expires response header field (Section 5.3) is present, use its value minus the value of the Date response header field". Without Date, the time the response was received is used.
  • R4 Expires, RFC 9111 5.3: "If a response includes a Cache-Control header field with the max-age directive (Section 5.2.2.1), a recipient MUST ignore the Expires header field." A shared cache also ignores it when s-maxage is present. "A cache recipient MUST interpret invalid date formats, especially the value "0", as representing a time in the past (i.e., "already expired")."
  • R5 Heuristic, RFC 9111 4.2.2: "A cache MUST NOT use heuristics to determine freshness when an explicit expiration time is present in the stored response." With Last-Modified, the page multiplies your fraction by the time between Last-Modified and Date. "This specification does not provide specific algorithms, but it does impose worst-case constraints on their results." Without Last-Modified the value is cache-specific and nothing is computed.
  • R6 Age, RFC 9111 4.2.3: apparent_age = max(0, response_time - date_value); response_delay = response_time - request_time; corrected_age_value = age_value + response_delay; corrected_initial_age = max(apparent_age, corrected_age_value), the conservative form, or "The corrected_age_value MAY be used as the corrected_initial_age"; resident_time = now - response_time; current_age = corrected_initial_age + resident_time. Then response_is_fresh = (freshness_lifetime > current_age), so a lifetime equal to the age is stale. A list-valued Age uses its first member (5.1).
  • R7 no-cache, RFC 9111 5.2.2.4: the response may be stored, but it "MUST NOT be used to satisfy any other request without forwarding it for validation and receiving a successful response". The qualified form no-cache="field" is, per the RFC's own note, often handled as if it were unqualified.
  • R8 Duplicates and conflicts, RFC 9111 4.2.1: for a repeated max-age or Expires, "either the first occurrence should be used or the response should be considered stale." Both readings are shown. Conflicting directives: the most restrictive is honored. A max-age that is not an integer: caches are encouraged to treat the response as stale.
  • R9 delta-seconds overflow, RFC 9111 1.2.2: "the cache MUST consider the value to be 2147483648 (2^31) or the greatest positive integer it can conveniently represent." Larger values are clamped and flagged.
  • R10 CDN column, RFC 9213: a targeted field is a Structured Fields Dictionary (RFC 8941 3.2). "If a targeted field in a given response is empty, or a parsing error is encountered, that field MUST be ignored by the cache". Otherwise the cache "MUST select the first (in target-list order) field with a valid, non-empty value and use its value to determine the caching policy for the response, and it MUST ignore the Cache-Control and Expires header fields in that response". "In particular, no-store and no-cache make max-age inoperative, and unrecognized extension directives are ignored." A decimal max-age is not coerced. With no usable targeted field, the CDN column equals the shared column.
  • R11 Dates, RFC 9111 4.2: matched case-insensitively, all three HTTP-date formats accepted, a zone other than GMT treated as invalid for expiration, local time zones never used.

Recognised but not computed: stale-while-revalidate and stale-if-error, must-understand, Vary (listed only), 304 responses, request directives and the Pragma field. This is an independent tool, not affiliated with the IETF or any CDN vendor.

4. Self test

Every case runs through the same evaluator as section 2: one fixture or more per rule R1 to R11, the five worked examples, and a property test that checks random max-age, s-maxage, Expires and Age combinations against a separately written reference of RFC 9111 4.2.1 and 4.2.3, from a fixed seed. The last row is a positive control: an assertion written to be wrong. If it ever reports that it held, the suite is not really running.