Breaking changes
Reverse proxy
- Shorter breaker and timeout defaults:
circuit_breaker.open_durationdefault is now5s(was30s) and upstreamconnection_timeoutdefault is now2s(was5s), so brief Docker Compose recreates and Apache graceful reloads recover quickly. Increase them explicitly if you need the old behavior.
Forwarded authentication
- Forwarded auth URI behavior change: Ferron no longer appends the original URI path to the redirect URL by default, instead relying on
X-Forwarded-Uriheader. Use theappend_urisubdirective to restore the previous behavior.
HTTP response control
ferron.rule_idno longer duplicates the status code: theferron.response.status_rule_matchedmetric now labels each match with the rule'snamewhen set, orstatus-<code>-rule-<n>(1-based rule position) otherwise. Update dashboards that filterferron.rule_idby bare status code to usehttp.response.status_codeor the new identifiers instead.
URL rewriting
rewrite_loglogsDEBUGinstead ofINFO: therewrite_logdirective now logs each rewrite operation to the error log atDEBUGlevel instead ofINFO, so that it does not pollute normal logs. Update log-level filters to includeDEBUGfor rewrite logs, or use the new structured log attributes instead.
Added
Reverse proxy
retry_intervaldirective: delay between same-upstream retry attempts plus up to 25% jitter (default200ms,"0s"disables waiting). Masks brief backend restarts such as Docker Compose recreates or Apache graceful reloads.
Forwarded authentication
append_urisubdirective: when enabled, the original URI path is appended to the redirect URL. This can be used to restore the previous default behavior before this version.
Observability
- HTTP/2 and HTTP/3 stream error logging: stream failures (for example malformed requests, flow-control violations, or resets) are now logged to the configured observability sinks with client and server addresses, similarly to connection errors. HTTP/2 stream errors use
error.typetcp_stream_errororunix_stream_error, and HTTP/3 stream errors usequic_stream_error. - Clearer HTTP/2 and HTTP/3 connection errors: connection-level protocol violations are now returned as errors (instead of closing silently), so they are logged as connection errors with a descriptive message.
HTTP cache
- LSCache staleness suffix support:
~sstaleness suffix is now parsed in LSCache compatibility, allowing stale cache entries to be purged by single tag or URL. X-LiteSpeed-Vary: value=support via request variables: responses declaringvalue=<name>are now stored instead of bypassed, partitioned per request by theset_varvariable<name>(empty when unset). Only one value dimension is supported (last wins); values are normalized and truncated past 256 characters.
HTTP response control
namesubdirective forstatusrules:statusblocks accept an optional plain-stringname, surfaced as theferron.rule_idmetric label so same-code rules can be told apart in dashboards.
URL rewriting
nameoption forrewriterules:rewriteblocks accept an optional plain-stringname, surfaced asferron.rewrite.rule_nameacross rewrite metrics, spans, logs, and access-log fields.- Per-rule rewrite attribution:
ferron.rewrite.rewrites_appliedandferron.rewrite.invalidnow carryferron.rewrite.rule_index(1-based rule position); spans, access logs, andrewrite_logoutput additionally report matched-rule counts and per-step from/to URLs for chained rewrites.
Abuse protection and rate limiting
log_rejectionsoption:abuse_protectionandrate_limitblocks accept an optionallog_rejectionsflag that re-enables per-request rejection debug logs for forensics (default off).
Changed
Reverse proxy
localhostexception from strict DNS resolution:localhostURLs are no longer resolved via strict DNS; they are treated as local addresses and bypassed to avoid unnecessary DNS lookups.- Stale pooled-connection forgiveness: a first failure on a reused keepalive connection (
SendRequestError) is no longer counted toward the circuit breaker when a same-upstream retry will be attempted. This prevents one dead socket closed during reloads from tripping the breaker when the retry succeeds.
URL rewriting
ferron.rewrite.rewrites_appliedcounts rule firings: the counter now increments once per matched rule (instead of once per rewritten request), so chained rewrites contribute one increment per step. Single-rule rewrites are unaffected.
Abuse protection and rate limiting
- Transition-only rejection logging: ban rejections and rate-limit rejections no longer emit a debug log per request, which could exhaust the observability pipeline under abuse waves. Bans now log
Ban triggered/Ban expiredtransitions plus aferron.abuseban.active_bansgauge; rate limiting logs the first rejection per key (then at most once per minute) without the raw key value. Set the newlog_rejectionsoption to restore per-request debug logs for forensics.
Fixed
Abuse protection and rate limiting
- Ban eviction race fix: observing an expired ban and evicting it is now atomic, so concurrent requests can no longer delete a freshly re-triggered ban, and each ban expiry is reported exactly once.
HTTP cache
- LSCache
stalepurge now serves stale: purge operations carrying thestalemarker expire matching entries in place instead of deleting them, so they keep serving stale while revalidating (or on error) when a stale window applies, and miss otherwise.
HTTP server core
- Jammed unquoted IP address fix: previously, unquoted IP addresses in
ferron.conffiles could be jammed together leading to unexpected behavior. - HTTP/2 flow control fixes: fixed a flow-control bug that caused stream resets when downloading large response bodies with
hyper(seezincio-httpchangelog). - HTTP/2 and HTTP/3 hardening: improved robustness of HTTP/2 and HTTP/3 server implementation (see
zincio-httpchangelog). - Active request metric decrement fix: previously, canceled HTTP/2 connections caused active request metric to not be decremented properly, leading to incorrect active request counts.
Admin API
- Admin API listen fail fix: previously, when configuration is reloaded, the admin API listener would sometimes fail to bind to the configured address due to a race condition.
Observability
- Prometheus reload listen fail fix: previously, when configuration is reloaded, the Prometheus listener would sometimes fail to bind to the configured address due to a race condition.
- Global span links fix: the server now fallback back into global span links (control plane metadata) properly.
Reverse proxying
- Reverse proxy stall fix: fixed stalls on some connections caused by aborted connections when the reverse proxy was waiting for a response from an HTTP/1.x upstream server.
- Circuit breaker metric fix: fixed missing circuit breaker metrics when circuit breaker is enabled but not yet opened, so that the
ferron.proxy.circuit_breaker.statemetric is always emitted. - Reverse proxy canceled span fix: the server now emits span attributes even when the reverse proxy request is canceled (due to timeout or explicit cancellation), so that the span is not empty and can be correlated with the request.