Cache rules change the default behavior for the requests they match. Use them to keep admin pages and logged-in visitors out of the cache, or to cache HTML.
How rules are evaluated
- A zone (and each name with its own list) has up to 25 rules, evaluated in order for
GETandHEADrequests. - When several rules match, the last matching rule wins for each setting. Put broad
cacherules first and thebypassrules that must always win after them. - A name either inherits the zone's rules that apply to it or has its own list, which replaces the zone's for that name.
Conditions
Every list you fill must match (AND); inside one list, any value may match (OR). A rule without conditions matches every request. Each list holds up to 32 values.
| Field | Dashboard | Matches when |
|---|---|---|
match.path_prefix | Path starts with | The path starts with one of the values (each starts with /). |
match.path_exact | Path is exactly | The path is exactly one of the values. |
match.extensions | File extension | The last segment's extension is one of the values, without the dot; "" = no extension (/, /about). |
match.cookie_present | Request has cookie | The request carries one of these cookies; a trailing * matches a prefix (wordpress_logged_in_*). |
match.query_present | URL has parameter | The URL has this query parameter, with or without a value; a trailing * matches a prefix. |
hosts | Names | Zone rules only: the names the rule applies to; empty = every name. |
Because the lists of one rule are combined with AND, put exact paths and prefixes in separate rules when either should match.
Actions
action | Dashboard | Effect |
|---|---|---|
bypass | Bypass cache | Never looked up or stored (DYNAMIC). Takes no parameters. |
cache | Eligible for cache | Eligible even if the extension isn't in the default list (HTML, JSON…), with optional parameters below. |
Parameters of a cache rule
| Parameter | Values | Effect |
|---|---|---|
edge_ttl.mode | respect_origin | The origin's headers; without them, the default TTLs (only on the default extensions). |
bypass_by_default | The origin's headers; without them, not stored. | |
override (with ttl_s) | Ignore the origin's Cache-Control and keep the response ttl_s seconds. Removes Set-Cookie from the stored copy and disables the deception armor for these requests. | |
edge_ttl.status | [{"from": 200, "to": 299, "ttl_s": 300}] | TTL per status code range, in any mode: -1 = don't store, 0 = store and always revalidate. Up to 10 ranges. |
browser_ttl | {"mode": "respect_origin"} or {"mode": "override", "ttl_s": N} | override raises the browser max-age to at least N. |
query | {"mode": …, "sort": …, "params": […]} | Query policy in the key for these requests. See Cache keys. |
serve_stale | {"while_revalidate": …, "on_error_max_s": …} | Stale content for these requests. |
vary_bypass_if_present | Lower-case request header names | See below. |
TTLs go from 1 second to 1 year; on_error_max_s up to 30 days.
Skip the cache on some request headers
vary_bypass_if_present (Skip the cache if the request has these headers) lists request headers, such as the rsc, next-router-state-tree, next-router-prefetch and next-router-segment-prefetch headers of Next.js client navigation. A request carrying any of them is neither looked up nor stored (DYNAMIC, detail=vary). Requests without them get the normal cached version, even if the origin lists those headers in Vary. accept-encoding, accept and origin can't be listed: they already vary the cache.
Templates
The dashboard (Caching › Cache rules › Start from a template) fills the rule editor; nothing changes until you save.
| Template | Rules |
|---|---|
| WordPress | Bypass /wp-admin/ and /wp-login.php, and visitors with WordPress or WooCommerce session cookies. Optionally also caches anonymous HTML for 5 minutes. |
| Next.js | Caches pages (paths without extension) for up to 10 minutes, only 200 answers, ignoring ad parameters in the key. Never caches /api/, login, account, cart or checkout paths, visitors with a session cookie or Next.js navigation requests (RSC headers, ?_rsc). |
| Static site | HTML for 5 minutes, served stale while it refreshes or if the origin fails. |
| Defaults only | Removes every rule. |
| Cache everything | Every response eligible, following the origin's Cache-Control. |
| Bypass cache for everything | Nothing is cached. |
Add rules
In Caching › Cache rules, select Add rule, fill When incoming requests match… and Then…, and Apply. Reorder with the arrows, then Save changes.
openwaf-cloud ctl zones set example.com 'cache.rules:=[
{"match": {"extensions": ["html", ""]}, "action": "cache",
"edge_ttl": {"mode": "override", "ttl_s": 300}},
{"match": {"path_prefix": ["/wp-admin/", "/wp-login.php"]}, "action": "bypass"},
{"match": {"cookie_present": ["wordpress_logged_in_*", "wp-postpass_*"]}, "action": "bypass"}
]' --reason "WordPress cache rules"curl -s -X PATCH https://dash.openwaf.dev/v1/orgs/$ORG/zones/$ZONE/edge \
-H "Authorization: Bearer $OPENWAF_TOKEN" \
-H "Content-Type: application/json" \
-H "X-Change-Reason: bypass admin" \
-d '{"cache": {"rules": [{"match": {"path_prefix": ["/wp-admin/"]}, "action": "bypass"}]}}'update_settings with {"zone": "example.com", "settings": {"cache": {"rules": [{"match": {"path_prefix": ["/wp-admin/"]}, "action": "bypass"}]}}, "reason": "bypass admin"}.
rules is always replaced as a whole: send the complete list; [] removes every rule. Rules without an id get r1, r2…
Check a rule
curl -sI https://www.example.com/ | grep -i -E 'x-cache-status|cache-status'For HTML, ask twice: the second answer should be HIT. A page that stays MISS or BYPASS isn't being stored; Cache-Status says why in detail. See Troubleshooting.