# Caching

> How Keenpix caches transforms and plays nicely with a CDN.

Canonical HTML: [https://keenpix.com/docs/concepts/caching](https://keenpix.com/docs/concepts/caching)
Author: Keenpix Team

Optimizing an image is expensive; serving it again should be free. Keenpix caches every
transform on disk and is built to sit behind a CDN.

## The cache key [#the-cache-key]

A result is keyed by everything that affects the output:

```text
project + url + full transform option object
```

Two requests with identical effective parameters share one cached file. Requests that
arrive while the first identical transform is still running share that same in-flight
work too.

<Callout title="In-flight coalescing">
  If many identical requests arrive during a cold cache (e.g. a popular image after a
  deploy), Keenpix runs the fetch + encode **once** and shares the result, instead of
  doing the same work N times.
</Callout>

## CDN-friendly responses [#cdn-friendly-responses]

Every successful response sets:

```http
Cache-Control: public, max-age=31536000, immutable
Accept-CH: Sec-CH-DPR, Sec-CH-Width, Sec-CH-Viewport-Width
Vary: Accept, Sec-CH-DPR, Sec-CH-Width, Sec-CH-Viewport-Width
```

Because the source URL and every transform live in the URL, the response is immutable.
Configure your CDN to cache `/img/*` responses, including the full query string. For
Cloudflare, that usually means a Cache Rule for the endpoint; orange-cloud proxying alone
is not enough for every dynamic-looking path.

`Vary: Accept` keeps per-format variants correct under `fmt=auto` only when your CDN
supports caching separate variants by `Accept`. On CDNs that do not, prefer explicit
`fmt=avif`, `fmt=webp`, `fmt=jpeg`, `fmt=png`, or `fmt=svg` URLs. Explicit formats improve cache
predictability, but they also disable browser-based AVIF/WebP negotiation for that URL.
`fmt=svg` is one of those explicit formats; `fmt=auto` never preserves SVG output.

For the first image request, and for a delivery hostname different from the page
hostname, also advertise the hints on the HTML response and delegate them with a
Permissions Policy, for example:

```http
Accept-CH: Sec-CH-DPR, Sec-CH-Width, Sec-CH-Viewport-Width
Permissions-Policy: ch-dpr=(self "https://images.example.com"), ch-width=(self "https://images.example.com"), ch-viewport-width=(self "https://images.example.com")
```

Browsers may omit hints for privacy or compatibility reasons, so Keenpix always
falls back to project defaults.

## Where does Keenpix store cached images? [#where-does-keenpix-store-cached-images]

Production uses memory → Dragonfly → Cloudflare R2 or MaxIO. Lower-tier hits are
promoted, durable writes happen first, and `KEENPIX_CACHE_DELETE_AFTER_MS`
removes an expired variant from the complete chain. Local source development
without those services uses the atomic disk cache at `KEENPIX_CACHE_DIR`.
See [Configuration](/docs/self-hosting/configuration) for size limits and
eviction.

The full effective transform object is in the cache key. That includes resolved
Client Hint dimensions and project watermark configuration, so neither feature
can cross-contaminate variants.