Added
HTTP caching
- Cache zones — the
cachedirective now supports azonesubdirective at host scope, allowing multiple hostnames to share a single in-memory cache store. Named zones can be pre-configured at global scope with custommax_entriescapacity. When a globalcache { max_entries }block exists without explicitzoneblocks, all hosts share a global zone by default. Hosts can opt out with an explicitzonedirective. - Generation-aware cache capacity —
max_entriesis now only evaluated on configuration reload instead of on every request, preventing premature LRU eviction when different host blocks specify different capacities for the same zone. - Zone attribute in cache metrics — all cache metrics (
ferron.cache.requests,ferron.cache.entries,ferron.cache.stores,ferron.cache.evictions,ferron.cache.purges) now include aferron.cache.zoneattribute identifying the zone the request belongs to.
Rate limiting
- Rate limit zones — the
rate_limitdirective now supports azonesubdirective at host scope, allowing multiple hostnames to share the same rate limit token bucket registries. Named zones can be defined at global scope. When a globalrate_limitblock exists without explicitzoneblocks, all hosts share a global zone by default. Hosts can opt out with their ownrate_limitblock. - Key extractor in fingerprint — the rate limit fingerprint now includes the key extractor type (
ip,uri,header), so rules with different key types no longer share the same registry. - Zone attribute in rate limit metrics — rate limit metrics (
ferron.ratelimit.rejected,ferron.ratelimit.allowed) and structured log events now include aferron.ratelimit.zoneattribute identifying the zone the request belongs to.
Static file serving
If-Rangesupport — TheIf-Rangeheader is now supported for conditional range requests (RFC 7233 §3.2), allowing clients to receive a partial response when the entity tag or modification date matches, or a full response when the representation has changed.
Fixed
Reverse proxy
- SRV priority/weight swap fix — SRV upstream resolution was reading the priority and weight fields in the wrong order, causing traffic to be routed to the wrong priority group. Priority and weight are now correctly interpreted per RFC 2782.
- Upgrade connection pool leak fix — HTTP 101 upgrade connections (WebSocket, etc.) were not decrementing the pool outstanding counter, permanently reducing available pool capacity by one per upgrade. The pool item now drops correctly, releasing the slot.
- Hop-by-hop header stripping on requests — the outgoing proxy request now strips hop-by-hop headers (
Connection,Keep-Alive,Transfer-Encoding,TE,Trailer,Proxy-Authorization,Proxy-Authenticate) per RFC 7230 §6.1, preventing clients from injecting these headers to influence backend behavior.
Static file serving
- HTTP range requests fix — HTTP range requests now correctly handle out-of-bounds ranges, returning
206 Partial Contentwith the available range instead of416 Range Not Satisfiable(per RFC 7233 §2.1). - On-the-fly compression allowed while using precompressed files — When serving precompressed files, the content is now compressed on-the-fly if precompressed files are not available, improving performance and reducing disk usage.
- Multipart byterange fix — Multipart range responses now include the required
bytesprefix inContent-Rangepart headers (per RFC 7233 §4.1) and correctly serve one additional byte per range to match inclusive-to-exclusive bounds (is_end_streamno longer signals end-of-stream while the last range's data is still being streamed). If-None-MatchPOST fix — POST requests with a matchingIf-None-Matchheader now correctly return412 Precondition Failedinstead of200 OK(per RFC 7232 §4.2).If-Match: *with non-GET/HEAD fix —If-Match: *now correctly passes for POST and other non-GET/HEAD requests when a representation exists, per RFC 7232 §3.1.- Invalid Range header handling — Syntactically invalid
Rangeheaders are now treated as absent (returning200 OK) instead of416 Range Not Satisfiable, per RFC 7233 §3.1. file_cache_controlpanic fix — Malformedfile_cache_controlconfig values can no longer panic the request handler; a validation warning now catches invalid characters.- Missing
Content-Lengthfor compressed responses — Non-identity (compressed and precompressed) file responses now include the correctContent-Lengthheader. bytes_sentmetric fix — Thebytes_sentmetric now reports the actual compressed file size for precompressed responses instead of the original file size.
HTTP caching
- Cache revalidation fix - when a cached response is revalidated, the HTTP status code is now preserved from the cached response instead of always using
200 OK. - Abuse protection fix - 403 Forbidden responses generated by abuse protection module are no longer cached (as they are served before the cache).
- Cache scope fix - in the previous version (Ferron 3.0.0-beta.3), caches were per-host instead of global, even when maximum cache items subdirective was global-only. This has been fixed to restore the behavior before 3.0.0-beta.3.
- Stale-while-revalidate inflight request fix - when a stale response is revalidated, the inflight request handling no longer causes possible hangs.
Expiresheader in past edge case fix - a response with a pastExpiresheader and noCache-Controldirectives was incorrectly cached for 5 minutes. This has been fixed to cache the response for 0 seconds (expires immediately) instead.- Stale-if-error correctness fix - a stale response is no longer served when
must-revalidateis set, per RFC 9111 §4.3.4. - Cache key fix - an attacker could craft a cookie value containing
&cookie:to collide with another user's cache key. This severe cache collision was fixed by using\0as the separator instead of&in the cache key. - 304 revalidation TTL fix - when a cached response is revalidated via a
304 Not Modifiedresponse, the stored response's TTL,stale-while-revalidate,stale-if-error, andmust-revalidatedirectives are now updated from the 304 response'sCache-Controlheaders per RFC 9111 §4.3.4. Previously onlyETagandLast-Modifiedvalidators were updated, causing the original TTL to persist even when the upstream sent a differentmax-age. - Cache variant metadata leak fix - the
variants_by_basemap used for cache lookups previously grew without bound, retaining variant records for all URLs ever cached even after entries were purged. This map is now cleaned up when all entries for a base key are removed via purge.