Default cache behavior

Which requests the edge cache considers, which responses it stores, how long it keeps them and what it never caches.

On this page

The cache decides twice: first on the request (is it eligible, so the cache is looked up?), then on the response (can it be stored, and for how long?).

Eligible requests

A request is looked up in the cache only if all of these hold. Otherwise it goes straight to the origin and is marked DYNAMIC.

  • The cache is on for the name, and development mode isn't.
  • The method is GET or HEAD, without an Upgrade header (WebSocket) and without a request body.
  • No bypass cache rule matches.
  • The path's extension is in the default list below, or a cache rule makes the request eligible.
  • The path isn't ambiguous: no ; path parameters, no encoded /, \, NUL, CR or LF, no backslash and no last segment ending in a dot (/x.css.).

The request's own Cache-Control and Pragma are ignored, so a client can't force the edge to refetch from your origin.

Default extensions

7z avi avif apk apng bin bmp bz2 class css csv doc docx dmg ejs eot eps exe flac gif gz ico iso jar
jpeg jpg js jxl m4a m4v mid midi mjs mkv mov mp3 mp4 ogg otf pdf pict pls png ppt pptx ps rar svg
svgz swf tar tif tiff ttf wasm webm webmanifest webp woff woff2 xls xlsx zip zst

plus robots.txt. HTML, JSON, XML, txt and source maps are not in the list: they need a cache rule.

Freshness: how long a response is kept

The edge takes the TTL from the first of these that the origin sends:

  1. OpenWAF-CDN-Cache-Control: a cache-control header for the OpenWAF edge only, never forwarded to browsers.
  2. CDN-Cache-Control (RFC 9213), forwarded unchanged.
  3. Cache-Control: s-maxage, then max-age.
  4. Expires.
  5. The defaults below, when none of the above is present.

Cache-Control is always forwarded to the browser as the origin sent it, unless you set a browser TTL.

Default TTLs

Without any freshness header, on the default extension list only:

StatusEdge TTL
200, 206, 3012 hours
302, 30320 minutes, only for requests without Cookie or Authorization
404, 4103 minutes
Anything elsenot stored

Known build directories of common frameworks get 7 days instead of 2 hours: /_next/static/, /_nuxt/, /_astro/, /_app/immutable/, /build/assets/ and /packs/, except deliberately mutable files such as version.json, latest.json, sw.js, service-worker, manifest.json, page-data/ and app-data.json.

A path made eligible by a rule (HTML, JSON…) gets no default TTL: it needs freshness from the origin or a TTL in the rule.

No object is kept longer than 30 days.

Responses that are never stored

These responses pass through to the visitor without being stored and are marked BYPASS:

  • Cache-Control: no-store or private. With private="Set-Cookie", the response is stored without that header.
  • A Set-Cookie header, unless a rule overrides the edge TTL (then Set-Cookie is removed from the stored copy).
  • A request with Authorization whose response doesn't say public, s-maxage or must-revalidate.
  • Vary naming anything other than Accept-Encoding, Accept (images only) or Origin, including Vary: * and Vary: Cookie. See Cache keys.
  • A status that can't be cached, a body larger than the maximum object size (100 MB by default) or a content encoding other than gzip or Brotli.
  • Personal documents: for pdf, doc(x), xls(x), ppt(x), csv, zip, 7z, rar, tar and gz, a request with Cookie or Authorization is stored only if the origin sent explicit freshness.
  • Pages generated by the edge itself: challenges, block pages, the access gate and redirects.

Cache deception armor

On by default. When the URL's extension promises one type but the origin answers text/html, application/json, application/xhtml+xml or text/plain (except robots.txt and .csv), the response isn't stored. This stops the classic attack where /account.php/x.css returns a personal HTML page that would otherwise be cached and served to others. Harmless mismatches such as application/octet-stream or a .jpg served as image/webp are allowed.

Revalidation and stale content

  • no-cache, max-age=0 or s-maxage=0 with an ETag or Last-Modified: the response is stored and revalidated with the origin on every use (If-None-Match / If-Modified-Since); a 304 saves sending the body again.
  • An expired object with validators is revalidated: REVALIDATED on a 304, EXPIRED on a new 200.
  • Stale while revalidate: with the origin's stale-while-revalidate, an expired object is served (UPDATING) while it refreshes in the background.
  • Stale if error: when the origin fails (network error, timeout, 500, 502, 503, 504), the expired object is served (STALE) for the origin's stale-if-error or, without it, up to 24 hours by default.
  • must-revalidate, proxy-revalidate, no-cache and s-maxage disable stale content.

Range, HEAD and concurrent misses

  • A GET with a single Range on a miss fetches the whole object once, stores it and answers the range as it arrives (206), so videos are cached too.
  • A HEAD is served from the cache when the object is there; otherwise it goes to the origin and nothing is stored.
  • When many visitors ask for the same missing object at once, only one request goes to the origin; the others wait up to 5 seconds and are served from the result.

Compression

The edge asks the origin for br, gzip and keeps one copy per URL in the encoding the origin chose. A visitor whose browser doesn't accept that encoding gets the copy decompressed on the fly; Vary: Accept-Encoding doesn't split the cache.

Last updated:

↑↓ to navigate↵ to openesc to close