105 lines
12 KiB
Markdown
105 lines
12 KiB
Markdown
# HTTP cache — HOT, COLD and invalidation
|
|
|
|
RustProxy provides an optional shared HTTP response cache with a bounded in-memory **HOT** tier and optional persistent-disk **COLD** tier. Both the global cache and a Vhost's effective policy must permit caching. **COLD storage is disabled by default**, and newly created Vhosts do not automatically cache dynamic content.
|
|
|
|
## Enable caching safely
|
|
|
|
1. Open **Settings → Cache**, enable the global cache and choose memory limits. Enable COLD only if you want durable disk storage and have enough private disk capacity.
|
|
2. On the appropriate Vhost, enable cache for public, repeatable responses. Restrict or disable it for login, personalized pages, uploads, APIs and responses carrying secrets.
|
|
3. Ensure the origin supplies a valid public freshness policy, or configure a safe default TTL where supported. `Cache-Control: private` and `no-store` must prevent shared storage.
|
|
4. Optionally enable HIT-only diagnostics. Send repeated identical requests using `curl`, then inspect **Cache → Domains / Stored objects** for `MISS → HIT`.
|
|
5. Review **Varnish-compatible cache policy**, enabled by default, then check TTL, revalidation and invalidation after updating the origin's content or configuration.
|
|
|
|
## Varnish-compatible cache policy (0.34.7)
|
|
|
|
**Settings → Cache → Varnish-compatible cache policy** (`cache.varnish_compatible`, **enabled by default**) avoids needless cache replacement on browser hard reloads. On a fresh object it ignores only the client's `Cache-Control: no-cache`, `max-age=0` and `Pragma: no-cache` for cache **lookup/freshness**. It does not remove or modify these request headers when fetching from the origin, and it does not alter cache identity or request conditionals.
|
|
|
|
Set **Default grace (seconds)** in Settings → Cache (default **60**, range 0..604800). This affects only eligible objects in compatibility mode and never overwrites explicit origin `stale-while-revalidate`, including `=0`. Use the adjacent **i** information icon for the short operator explanation (PL/EN). Disabling the option restores the original strict client reload semantics. Changing modes invalidates currently stored representations across HOT/COLD so they are refilled under one consistent policy; no process restart is needed. Existing JSON configurations with no field inherit `true`. The setting also applies to eligible resources of the embedded Web Server.
|
|
|
|
| Condition | Enabled: Varnish-style optimized, RustProxy protections retained | Disabled: legacy strict |
|
|
|---|---|---|
|
|
| Client `no-cache`, `max-age=0`, `Pragma: no-cache` | **Fresh HIT** without replacing object or resetting `Age` / HIT counter | Can force origin revalidation (and `200` replacement without an origin validator) |
|
|
| Client `no-store` | **BYPASS**: do not use or populate a shared representation | Same |
|
|
| Client `only-if-cached` | **Fresh/stale eligible HIT**, or **504** if no reusable representation; never fetch origin on MISS | Same |
|
|
| Client `min-fresh`, `max-stale`, positive `max-age` | Keep constraints; an otherwise fresh object can be rejected by `min-fresh`, while eligible stale entries can be served by `max-stale` | Same |
|
|
| Origin `no-cache` with `ETag`/`Last-Modified`, fallback TTL = `0` | Store for **conditional revalidation** (`304`) but never fresh-HIT without checking origin | Same |
|
|
| Origin **`public, no-cache`** and explicit fallback TTL `> 0` | Treat the administrator's configured **Fallback TTL for public responses** as shared freshness (capped by max TTL), even when the origin has no validator; this explicitly overrides origin `no-cache` | Validator-only revalidation, no override |
|
|
| Origin `max-age=0` / already expired, with `stale-if-error`, a validator or client `max-stale` | Retain the expired representation; it does not become a fresh HIT. Can serve stale on an eligible error, revalidate, or honor explicit client allowance | Same |
|
|
| Origin `Surrogate-Control: no-store` | Do not store, even when a fallback TTL is configured; this is an additional compatibility-mode safeguard | Legacy response policy |
|
|
| Origin `private`, `no-store`, `Set-Cookie`, invalid `Vary` | Must not enter shared cache; configured static-cookie exceptions do not override response privacy | Same |
|
|
| Eligible cached object after TTL | Implicit background-refresh grace up to **60 seconds by default** (configurable with `cache.varnish_grace_seconds`, `0` disables it) **only** for entries initially fresh with **no** origin `stale-while-revalidate` directive; explicit `stale-while-revalidate=0` disables implicit grace. Existing `must-revalidate` / `proxy-revalidate` restrictions still apply | No implicit grace |
|
|
| Cookies, Authorization, `Vary`, per-vhost exclusions/force, query/range restrictions, BAN/PURGE, request collapse, transforms, compression, HOT/COLD | Existing RustProxy mechanisms remain in effect | Same |
|
|
|
|
**Important:** using a positive fallback TTL to reuse an origin response marked `public, no-cache` is an **explicit administrative freshness override**. It can serve outdated content for that duration; leave fallback TTL at `0` if the backend's `no-cache` must always be honored. The fallback override applies only to responses carrying `public`, not to private/no-store/Set-Cookie responses or `Surrogate-Control: no-store`. The configured `max_ttl_seconds` still caps remaining freshness. Without fallback TTL, origin `no-cache` still requires an origin validator to be stored. An origin `max-age=0` is never made fresh merely because `stale-if-error` is present.
|
|
|
|
This is **a Varnish-inspired policy subset, not full Varnish binary/VCL compatibility**. RustProxy deliberately keeps an explicit zero-by-default fallback TTL instead of Varnish's implicit 120-second default, retains client controls beyond hard reload, enforces multi-tenant and cookie/Authorization safety rules, and does not implement hit-for-miss/hit-for-pass markers. The current compatibility-specific `Surrogate-Control` safeguard handles `no-store`; it does **not** interpret every Surrogate-Control extension or use Surrogate-Control `max-age` as a TTL override. Different VCL configurations can behave differently.
|
|
|
|
To verify with real GET requests (not `curl -I -X GET`):
|
|
|
|
```bash
|
|
URL=https://example.com/ui/style.css
|
|
curl -sS --compressed -D - -o /dev/null "$URL" | grep -iE '^(HTTP/|age:|x-cache:|x-cache-hits:|x-cache-reason:)'
|
|
curl -sS --compressed -H 'Cache-Control: no-cache' -H 'Pragma: no-cache' -D - -o /dev/null "$URL" | grep -iE '^(HTTP/|age:|x-cache:|x-cache-hits:|x-cache-reason:)'
|
|
curl -sS --compressed -D - -o /dev/null "$URL" | grep -iE '^(HTTP/|age:|x-cache:|x-cache-hits:|x-cache-reason:)'
|
|
```
|
|
|
|
With the policy enabled, the middle request stays `HIT` if the entry is fresh. Check safeguards separately: an uncached URL with `Cache-Control: only-if-cached` must return **504** without reaching the backend; a stale `only-if-cached` HIT must **not** start background validation/proactive refresh. `min-fresh` and `max-stale` still constrain stale reuse: an explicit `max-stale=0` must not be bypassed by the configured implicit grace. `Cache-Control: no-store` must bypass an existing fresh entry.
|
|
|
|
## Vhost editor options
|
|
|
|
In **Vhosts → Cache**, overrides for static extensions, bypass path prefixes and unsafe force targets each place the enable/override checkbox immediately above the associated multiline list. The fields are disabled while their individual override is off; their saved values and cache policy are unchanged. Never enable unsafe forced caching for personalized responses.
|
|
|
|
## Stored object rules
|
|
|
|
- Reuse is scoped by frontend scheme, Host, exact client URL, Vhost/content policy and eligible `Vary` dimensions. HTTP and HTTPS objects do not cross-share.
|
|
- GET and suitable HEAD requests may reuse a full object; changing request methods do not silently invalidate all cached GET results.
|
|
- Authorization, dynamic cookies, `Set-Cookie`, sticky-affinity cookies, Tinyauth and other private signals normally cause **BYPASS**. A limited static-file cookie exception exists for configured static extensions and does not override `private`, `no-store`, `Set-Cookie` or `Vary: Cookie`.
|
|
- Origin status, response size, TTL, unsupported metadata, range/conditional semantics and request/client cache directives can prevent fill or require validation.
|
|
- A Web Service selected as a Vhost source uses the same cache policy for eligible local files. File changes can be checked with `ETag` / `Last-Modified`; a 304 validation may refresh a stored body without downloading it again.
|
|
|
|
## Freshness and diagnostics
|
|
|
|
| State | Interpretation |
|
|
|---|---|
|
|
| `MISS` | No suitable fresh cached representation was found; the origin was consulted. The response may still be ineligible for storage. |
|
|
| `HIT` | A matching stored object was reused. |
|
|
| `BYPASS` | Cache was disabled or a policy intentionally skipped it (for example authorization, cookies or a path exclusion). |
|
|
| Revalidated | An existing representation was checked against the origin; a `304` can retain the body. |
|
|
|
|
`Age` is total HTTP age, not only the time spent in RustProxy. The optional `X-Cache-Resident-Age` reports the cache residency interval. Debug headers such as `X-Cache`, `X-Cache-Hits` and reason labels are optional and should be enabled deliberately. With `varnish_compatible=true`, browser hard reloads do not force revalidation while an object remains fresh. In strict mode hard reloads can force origin revalidation; compare with `curl` when investigating.
|
|
|
|
Example, replacing the listener and Host values with yours:
|
|
|
|
```bash
|
|
for n in 1 2 3; do
|
|
curl -sS -D - -o /dev/null -H 'Host: app.example.com' \
|
|
http://127.0.0.1:8080/public.css
|
|
sleep 2
|
|
done
|
|
```
|
|
|
|
Look for cache reason labels including `request_cookie`, `request_authorization`, `response_cache_control`, `route_disabled` and `cache_hit` when diagnostic headers are enabled. A `MISS` alone does not prove the object was admitted to HOT/COLD.
|
|
|
|
## Advanced behavior
|
|
|
|
- **Budget limits:** HOT RAM capacity, object count/size, transient fill memory and optional COLD byte/object limits. Configure based on real workload and monitor disk/RSS.
|
|
- **HOT/COLD movement:** entries may be demoted, recovered or expired; a periodic/explicit COLD cleanup is available. Cache storage is not a permanent archive.
|
|
- **Request collapsing:** eligible concurrent MISS requests can share a bounded fill, limiting origin bursts.
|
|
- **Stale-if-error and refresh:** conditional options can help on eligible backend failures, but are not an unconditional stale-serving guarantee. Web Server local-file validation has a distinct policy.
|
|
- **Compression/transformation:** cached variants and validators must remain compatible with client encoding and content-modification settings.
|
|
|
|
## Clear or invalidate content
|
|
|
|
Authenticated Cache page/API operations can purge selected entries, domains or larger scopes. The optional public HTTP `PURGE`/`BAN` interface is **disabled by default**. Enable it only by listing trusted client IPs/CIDRs under **Settings → Cache → HTTP BAN / PURGE IP allowlist**; operations are additionally subject to Vhost binding and normal access controls. `PURGE` addresses an exact URL; `BAN` applies the supported bounded matching rules. Never allow untrusted Internet clients to purge all tenants' objects.
|
|
|
|
In 0.34.0, changing operational Vhost settings is intended to preserve compatible HOT/COLD objects; content-affecting changes invalidate affected scopes and COLD carries an additional fingerprint against stale resurrection. **This new behavior still needs release-level verification**, so validate cache after configuration edits and during upgrades. Earlier persistent cache entries may need refilling following format/compatibility transitions.
|
|
|
|
## Troubleshooting
|
|
|
|
- **Always BYPASS:** check both cache enable switches, login/cookies, exclusion rules, methods, Tinyauth and sticky sessions.
|
|
- **MISS never becomes HIT:** check response `Cache-Control`, status, object size, `Vary`, `Set-Cookie`, incomplete origin streams and whether the same scheme/Host/query is used.
|
|
- **Content unexpectedly old:** inspect Age, TTL, validators, stale rules and previous invalidation; purge only affected content after confirming its identity.
|
|
- **Memory/disk pressure:** review HOT/COLD budgets and cleanup, rather than increasing limits without measurements.
|
|
|
|
See [Monitoring](MONITORING.md) for cache telemetry and [Security](SECURITY.md) for privacy boundaries.
|