12 KiB
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
- 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.
- On the appropriate Vhost, enable cache for public, repeatable responses. Restrict or disable it for login, personalized pages, uploads, APIs and responses carrying secrets.
- Ensure the origin supplies a valid public freshness policy, or configure a safe default TTL where supported.
Cache-Control: privateandno-storemust prevent shared storage. - Optionally enable HIT-only diagnostics. Send repeated identical requests using
curl, then inspect Cache → Domains / Stored objects forMISS → HIT. - 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):
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
Varydimensions. 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 overrideprivate,no-store,Set-CookieorVary: 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:
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 for cache telemetry and Security for privacy boundaries.