# Cache keys

> How the edge decides that two requests share a cached object, how query strings enter the key and which Vary headers it supports.

The **cache key** identifies a cached object. Two requests with the same key get the same cached response.

## What's in the key

| Part | How it's normalized |
|---|---|
| Site | Each proxied name is its own site, so names never share objects. |
| Host | Lower case, without port or trailing dot. |
| Path | `%XX` escapes in upper case, escapes of unreserved characters decoded, `.` and `..` segments resolved. `//` is kept and reserved characters stay as sent (`%2F` and `/` are different). The same path is sent to the origin. |
| Query string | According to the [caching level](#query-string). |
| Variant | Only when the origin sends a supported [`Vary`](#vary). |

Cookies, the `Authorization` header and the OpenWAF challenge and access-gate cookies never enter the key. Keys longer than 4 KB aren't cached.

Changing the query settings of a site or its rules changes its keys, which acts like a purge for that site.

## Query string

`cache.query.mode`, called **Caching level** in the dashboard, decides which part of the query string goes into the key:

| Mode | Dashboard | Key |
|---|---|---|
| `all` (default) | Standard: the whole query string | The whole query, exactly as sent. `?v=1` and `?v=2` are different objects. |
| `ignore` | Ignore query string (static files) | No query, but only for the default static extensions. Rule-eligible paths keep their query. |
| `none` | No query string: cache only requests without one | Requests with a query aren't cached at all (`DYNAMIC`). |
| `include` | Only these parameters | Only the parameters in `cache.query.params`. |
| `exclude` | All parameters except these | Every parameter except those in `cache.query.params`, for example `utm_*`, `fbclid`, `gclid`. |

A trailing `*` in `params` matches a prefix. With `cache.query.sort` set to `true`, the parameters are sorted, so `?b=1&a=2` and `?a=2&b=1` share one object. A cache rule can set its own query policy for the requests it matches.

> **Tip**
>
> Keep the query in the key for assets versioned by query string (`style.css?ver=6.4`) and for image optimizers such as `/_next/image?url=…&w=…`. Use `exclude` to drop marketing parameters.


## Vary

The edge stores at most one variant per value of the supported `Vary` headers:

| `Vary` header | Handling |
|---|---|
| `Accept-Encoding` | Doesn't split the cache: one copy, decompressed on the fly for visitors who need it. |
| `Accept` | Only for `image/*` responses. Normalized to `avif`, `webp` or `other`, enough for image optimizers. |
| `Origin` | Normalized to `scheme://host[:port]`. Also applied when the response has an `Access-Control-Allow-Origin` other than `*`, even without `Vary: Origin`, so one site's CORS answer is never served to another. |
| Anything else, including `*`, `Cookie` and `Authorization` | The response isn't stored (`BYPASS`). |

A cache rule can list request headers in `vary_bypass_if_present` (for example the `RSC` and `Next-Router-*` headers of Next.js). A request carrying any of them skips the cache, and a `Vary` from the origin that names them no longer prevents storing the normal version. See [Cache rules](https://developers.openwaf.dev/cache/cache-rules/#vary-bypass).

## Headers removed before the origin

On eligible requests the edge removes headers that aren't in the key but that an origin might reflect into the page, a common cache-poisoning vector:

`X-Original-URL`, `X-Rewrite-URL`, `X-HTTP-Method-Override`, `X-HTTP-Method`, `X-Method-Override`, `X-Host`, `X-Forwarded-Scheme`, `X-Forwarded-Server` and `X-Forwarded-Port`.
