Response caching
An RFC 9111 shared cache in front of your app — policy per service, storage per proxy.
The model#
dash-proxy can run an RFC 9111 shared cache in front of your app — the standard HTTP caching model, driven entirely by the response headers your app already sets. Nothing is stored unless the app marks a response public with an s-maxage or max-age, and a response carrying Set-Cookie is refused unless you say otherwise — a shared cache replaying one client's cookie to the next is the worst thing it could do.
The configuration splits in two, on the policy/mechanism line:
proxy: cache:— the policy, per service: what this app allows to be stored.proxy: run: cache:— the storage, proxy-wide: where entries live for every service on the proxy.
With a load balancer, the cache runs there — the one layer that sees the whole fleet.
The policy (per service)#
Only enabled is required; everything else keeps dash-proxy's own default until you set it:
proxy:
cache:
enabled: true
max_ttl: 300 # cap the lifetime the app asks for (seconds)
max_body: 1_048_576 # largest response body to store (bytes)
max_variants: 8 # representations one URL may hold under Vary
vary_headers: # request headers added to EVERY cache key
- Accept-Encoding
vary_cookies: # cookie names added to EVERY cache key
- locale
allow_set_cookie: false # refuse Set-Cookie responses (the safe default)max_ttl means one mistaken Cache-Control cannot pin content until the next deploy. Responses bigger than max_body still reach the client — they are just not kept. Headers the app already names in Vary are keyed automatically per URL; vary_headers moves a header into the key for every path in the service, which is usually not what you want.
The storage (proxy-wide)#
proxy:
run:
cache:
store: redis://cache.example.com:6379/0 # or `memory` (the default)
store_timeout: 2
memory_size: 134_217_728memory is a per-node cache. A redis:// / rediss:// URL is shared by every proxy pointed at it, so one fetch warms the whole fleet. The URL is delivered as CACHE_STORE in a 0600 env file on the host — never on the command line — so a password in it stays out of process listings and the audit log. Changing only the URL's value does not move the drift digest: run dash proxy reboot after rotating it.
store_timeout is the fail-open guard for a shared store: how long the store may take to answer before the request goes to the app instead. A slow or down store costs a cache hit, never a failed request.
Administering it#
dash proxy cache stats # what the cache is holding
dash proxy cache stats --count # entries and bytes per service
dash proxy cache stats --json # the raw report
dash proxy cache purge # drop this app's cached responses
dash proxy cache purge --path-prefix /assetsBoth run on the layer that owns the cache — the load balancer when load balancing, else each proxy host.
When it is not caching#
A cache that quietly stores nothing is the usual first surprise. Start with dash proxy cache stats; dash-proxy also explains every refusal — check dash proxy logs for the reason, and the cache_refusals_total Prometheus metric (by reason) if you run with metrics_port. The common reasons:
- no
Cache-Control: public, max-age=...on the app's response, - a
Set-Cookieheader (withallow_set_cookie: false), - a body over
max_body, variant_limitfrommax_variants.