Files
RustProxy/docs/CACHE.md
T
2026-10-22 15:31:47 +02:00

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

  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):

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:

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.