Configuration: HTTP cache
This page documents the cache directive for configuring Ferron’s in-memory HTTP response cache. The cache stores complete GET response representations, serves HEAD from cached GET metadata, follows standard HTTP caching semantics by default, and understands a subset of LiteSpeed Cache response headers for LSCache-aware applications.
The cache applies to final HTTP responses produced by static file serving, reverse proxying, and other response stages.
- For static file cache headers such as
file_cache_controlandetag, see Static file serving. - For response headers and reverse proxy configuration, see HTTP headers and CORS and Reverse proxying.
cache
{
cache {
max_entries 2048
}
}
example.com {
cache {
max_response_size 1048576
litespeed_override_cache_control false
vary Accept-Encoding Accept-Language
ignore Set-Cookie
}
location /admin {
cache false
}
}At HTTP host scope, cache can be written either as a block or as a boolean flag. Block form enables caching for that scope and configures nested directives. Boolean form is useful when you want to enable or disable inherited caching without changing any nested settings.
Global cache block
Use the global cache { ... } block to configure shared cache capacity.
| Nested directive | Arguments | Description | Default |
|---|---|---|---|
max_entries | <int> | This directive specifies the maximum number of response entries stored in the shared in-memory HTTP cache. Setting this directive to 0 keeps the module loaded but prevents new entries from being stored. | 1024 |
Configuration example:
{
cache {
max_entries 4096
}
}Global cache { ... } blocks are only for shared cache sizing — they do not enable caching for HTTP hosts by themselves.
HTTP host cache block
Use the HTTP host cache { ... } block to enable caching and tune how responses are stored for that host or matching location.
| Nested directive | Arguments | Description | Default |
|---|---|---|---|
max_response_size | <int> | This directive specifies the maximum response body size, in bytes, that can be buffered and stored in the cache. Responses larger than this limit are still served, but they are not stored. | 2097152 |
litespeed_override_cache_control | [<bool>] | This directive specifies whether X-LiteSpeed-Cache-Control overrides standard response caching headers such as Cache-Control and Expires when Ferron decides whether to store a response and what TTL to use. This mode is intentionally non-standard and is intended only for applications that expect LiteSpeed-style cache semantics. | false |
emit_litespeed_headers | [<bool>] | This directive specifies whether the X-LiteSpeed-Cache-Control response header should be emitted when serving a cached response. | false |
purge_method | [<bool>] | This directive specifies whether the PURGE HTTP method is accepted for cache invalidation. When enabled, requests with method PURGE to a given URL will remove all cached entries matching that URL. This directive requires either HTTP basic authentication or the purge_allowed_ips directive; unauthenticated requests from non-allowed IPs are rejected with a 403 Forbidden response. | false |
purge_allowed_ips | <string> [<string> ...] | This directive specifies one or more IP addresses or CIDR ranges that are allowed to send PURGE requests. When non-empty, only requests from these IPs are allowed (unless the request is already authenticated via HTTP basic authentication). This directive can be specified multiple times. | none |
vary | <string> [<string> ...] | This directive specifies additional request headers that are added to the cache key, alongside any standard Vary response headers returned by the origin. This directive can be specified multiple times. | none |
ignore | <string> [<string> ...] | This directive specifies response headers that are removed from the stored cache representation while leaving the live response unchanged. This directive can be specified multiple times. | none |
Configuration example:
example.com {
cache {
max_response_size 2097152
litespeed_override_cache_control
emit_litespeed_headers
vary Accept-Encoding Accept-Language
ignore Set-Cookie
}
}litespeed_override_cache_control makes Ferron treat X-LiteSpeed-Cache-Control as overriding standard HTTP caching rules. It is intentionally non-compliant with RFC 9111 — enable it only when the upstream is written for LiteSpeed-style cache semantics. Request-side directives such as Cache-Control: no-cache and Pragma: no-cache still affect cache lookup behavior normally.
Boolean cache form
| Form | Description | Default |
|---|---|---|
cache | Enables caching for the current HTTP host or location scope. | false |
cache true | Explicitly enables caching for the current scope. | false |
cache false | Disables caching for the current scope, which is useful for overriding an inherited cache { ... } block. | false |
Behavior
Cache eligibility
- Only
GETandHEADrequests perform cache lookups. HEADrequests reuse cachedGETrepresentations and return only headers.- Non-
GETresponses are not stored, but they may still trigger LSCache-compatible purge headers. - Responses with
Vary: *are never stored. - Built-in error responses generated after the main HTTP pipeline are not currently stored.
PURGE method cache invalidation
When the purge_method subdirective is enabled, Ferron accepts the PURGE HTTP method for cache invalidation. A PURGE request to a specific URL removes all cached entries (both public and private) matching that URL, causing subsequent requests to fetch fresh content.
Security:
PURGE requests must be either:
- Authenticated via HTTP basic authentication (the
basic_authdirective), or - Originating from an IP address matching the
purge_allowed_ipslist.
If neither condition is met, Ferron returns a 403 Forbidden response. This ensures that cache purging is never accidentally left unsecured.
Example using trusted IP list:
example.com {
cache {
purge_method
purge_allowed_ips "127.0.0.1" "10.0.0.0/8"
}
}Example using basic authentication:
example.com {
cache {
purge_method
}
basic_auth {
users {
user "$argon2id$..."
}
}
}Example request:
PURGE /blog/post-123 HTTP/1.1
Host: example.comPublic and private cache behavior
- Public responses containing
Set-Cookieare not stored. - Private responses are partitioned by client context. Ferron currently uses the client IP address, the authenticated username when available, and detected private cookies.
- If Ferron cannot determine a narrower private cookie set, it falls back to all request cookies for the private cache key.
LSCache-compatible response headers
When the cache module is enabled, Ferron understands the following response headers from upstream applications and origin handlers:
| Header | Description | Notes |
|---|---|---|
X-LiteSpeed-Cache-Control | Controls cache scope and TTL using LSCache-style directives such as public, private, max-age, s-maxage, no-cache, and no-store. | By default, standard HTTP caching rules still take precedence. Enable litespeed_override_cache_control to prefer this header instead. |
X-LiteSpeed-Vary | Adds LSCache-style vary dimensions. | cookie=<name> is supported. value=<name> is not supported yet and causes Ferron to skip cache storage for that response. |
X-LiteSpeed-Tag | Assigns tags to cached responses so they can be purged later. | On private responses, public: prefixes remain public tags. |
X-LiteSpeed-Purge | Purges cached responses by tag, URL, or wildcard. | The stale marker currently falls back to an immediate hard purge. |
LSC-Cookie | Adds cache-safe cookie replay metadata. | Ferron converts this header to Set-Cookie before sending the response. |
X-LiteSpeed-Cache | Exposes cache hit, miss, or bypass status on outgoing responses. | Ferron sets this header itself (if enabled). Origin-provided values are ignored. |
X-LiteSpeed-Vary: value=... is not supported yet because Ferron does not currently have a request-time equivalent of LiteSpeed’s rewrite-rule vary environment values. The ignore directive affects only the stored representation — the live response sent to the client still includes those headers unless another module removes them.
Observability
Metrics
The cache module emits the following metrics:
| Metric | Type | Attributes | Description |
|---|---|---|---|
ferron.cache.requests | Counter | ferron.cache.result, ferron.cache.scope | Cache hits, misses, and bypasses |
ferron.cache.entries | Gauge | — | Current number of cached entries |
ferron.cache.stores | Counter | ferron.cache.scope | Responses stored in the cache |
ferron.cache.evictions | Counter | ferron.cache.reason ("expired" or "size") | Entries evicted from the cache |
ferron.cache.purges | Counter | ferron.cache.scope | Entries purged through LSCache-compatible controls |
Logs
DEBUG— logged when Ferron skips cache storage becauseX-LiteSpeed-Vary: value=...is not supported yet.DEBUG— logged when Ferron skips cache storage because the response body exceedscache.max_response_size.DEBUG— logged when Ferron performs a purge throughX-LiteSpeed-Purge.DEBUG— logged when Ferron performs a purge throughPURGEHTTP method.DEBUG— logged when Ferron receives an LSCachestalepurge marker and falls back to a hard purge.
Structured logs
| Description (summary) | Level | Attributes |
|---|---|---|
| Skipping cache store because response body exceeded maximum size | DEBUG | - |
| Skipping cache store because X-LiteSpeed-Vary is not supported yet | DEBUG | - |
| Cache purged via LSCache controls | DEBUG | cache.purged.count (purged cache entries) |
| Cache purged via PURGE method | DEBUG | cache.purged.count (purged cache entries) |
| LSCache stale purge marker ignored | DEBUG | - |
Best practices
The following best-practice checks are reported by ferron doctor for directives on this page.
litespeed_override_cache_control— This makes LiteSpeed cache headers override standard HTTP cache policy. Enable only for applications that require LiteSpeed-compatible semantics.purge_methodwithout access control — Cache purging enabled withoutpurge_allowed_ipsorbasic_authin the same scope allows unauthenticated cache invalidation.purge_allowed_ipswith wildcard — Allowing every source address for cache purging should be restricted to trusted operators or internal networks.