100 lines
10 KiB
Markdown
100 lines
10 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`, **on by default**) changes *request-side* cache lookup, not authentication or the cache key. Disabling it restores the earlier strict policy without a service restart. **Changing this policy invalidates cached representations** to avoid mixing storage semantics across modes; allow time for HOT/COLD to refill. Effective settings also apply to eligible embedded Web Server resources and HOT/COLD entries. Existing JSON configs with no new field inherit `true`.
|
|
|
|
| Condition | Enabled: Varnish-style | Disabled: legacy strict |
|
|
|---|---|---|
|
|
| Client `Cache-Control: no-cache`, `no-store`, `max-age=0`, `min-fresh`, `max-stale` | Ignore client cache directives for lookup and freshness; a **fresh** object stays HIT and keeps `Age` and hit count | Revalidate/bypass or constrain freshness as instructed by the client |
|
|
| Client `Pragma: no-cache`, `only-if-cached` | Ignore both; do not force backend or a synthetic 504 purely because of these directives | Revalidate or give 504 for a missing only-if-cached representation |
|
|
| Origin `Cache-Control: no-cache` | Do not store, like Varnish's built-in VCL | Store only with a validator and require revalidation per request |
|
|
| Origin `Surrogate-Control: no-store` | Do not store | Existing RustProxy behavior |
|
|
| Origin zero/nonpositive *remaining* TTL | Do not store | May retain a zero-TTL representation for revalidation; never a fresh HIT |
|
|
| Expired but eligible object | Allow up to 10 seconds of background-refresh grace (or explicit larger `stale-while-revalidate`), unless `must-revalidate` / `proxy-revalidate` forbids stale delivery | Only explicitly configured stale/grace conditions |
|
|
| Origin `private`, `no-store`, `Set-Cookie`, `Vary: *`; client Authorization | Existing RustProxy restrictions **always** apply | Same |
|
|
| Static-file cookie-ignore allowlist, custom bypass/force rules, TTL caps, BAN/PURGE, request collapse, compression/transform, HOT/COLD, tenant isolation | Unchanged RustProxy mechanisms | Same |
|
|
|
|
For `/ui/style.css` with `Cache-Control: public, max-age=600` and **no** origin ETag/Last-Modified, a hard reload with `Cache-Control: no-cache` should now remain `HIT` while its cached representation is fresh. The request headers are *not* stripped before forwarding when an origin fetch actually occurs; RustProxy only ignores them for its own lookup/freshness checks. Client `If-None-Match`/`If-Modified-Since`, range conditions, URL query, `Host` and `Vary` are preserved. Strict mode remains available for installations requiring clients to force validation.
|
|
|
|
This is **a documented policy subset, not a Varnish binary/VCL clone**. RustProxy deliberately keeps an explicit zero-by-default fallback TTL (no implicit Varnish 120s TTL), configured cookie exceptions, multi-tenant security, stale prohibitions for `must-revalidate`, and no Varnish-style hit-for-miss/hit-for-pass markers. Not every Varnish configuration behaves like the default built-in VCL. Cookies continue to be omitted from the cache key *only* for the already-configured static-file exception; do not enable that exception for personalized static endpoints. A no-cache **origin response** must not be made cacheable merely to gain HITs.
|
|
|
|
Use a three-request GET comparison with `curl` (replace URL with your static resource):
|
|
|
|
```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:)'
|
|
```
|
|
|
|
Enable diagnostic headers if needed. With compatibility **on**, the middle response should be `HIT`, and Age should not reset on a fresh object. With compatibility **off**, it can be `MISS` and reset if no origin validator is available. Explicit PURGE/BAN and stale/expired TTL remain authoritative.
|
|
|
|
## 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.
|