Ferron 3 change log

Stay updated with a Ferron 3 change log, featuring bug fixes, new features, and enhancements for each release.

Ferron 3.0.0-rc.3

Released in September 6, 2026

Added

FastCGI

  • Support for multiple FastCGI backends: multiple FastCGI backends can now be configured using multiple fcgi or fcgi_php directives.

Changed

HTTP cache

  • Automatic _lscache_vary vary cookies: request cookies with names starting with _lscache_vary (or the _litespeed_vary alias) are now part of the cache key without explicit configuration, matching LiteSpeed behavior. X-LiteSpeed-Cache-Control: no-vary disables all cookie-based vary dimensions for the response.

FastCGI

  • fcgi_php subdirectives: the fcgi_php directive now supports same subdirectives as fcgi (except extension and pass). This allows for more fine-grained control over PHP FastCGI requests.

Fixed

HTTP cache

  • HTTP cache coalesce timeout fix: coalesce timeout in HTTP cache no longer causes server crashes.
  • Default HTTP cache TTL fix: default HTTP cache TTL is now 10% of the time since the resource was last modified (RFC 9111) or disabled (LSCache compatibility). This improves cache consistency.

HTTP compression

  • HTTP upgrade fix: HTTP dynamic content compression is no longer applicable for 101 (Switching Protocols) responses for HTTP upgrades to work correctly.

Ferron 3.0.0-rc.2

Released in September 4, 2026

Fixed

HTTP server core

  • HTTP/2 empty body error fix: HTTP/2 requests with empty bodies no longer cause server errors. This is the only significant change in the emergency Ferron 3.0.0-rc.2 release.

Ferron 3.0.0-rc.1

Released in September 4, 2026

Changed

Automatic TLS

  • ACME fallback improvements: Ferron now tries each configured fallback provider when the primary provider fails when provisioning a certificate (not just on account creation).
  • ACME certificate installation: Certificates are now reused from cache early before ACME provisioning cycles to mitigate TLS certificate errors after reloading the configuration.

OCSP stapling

  • OCSP status logging and metrics: added ferron.ocsp.cert.status attribute to log records and metric labels, allowing the certificate’s OCSP status (good, revoked, unknown) to be logged and monitored.

Observability

  • No metric exemplars linking to sampled-out traces: metric exemplars are no longer linked to traces that are sampled out (and thus not exported) to avoid misleading observability tools and reduce debugging friction.

Reverse proxy

  • Connection pool racing improvements: when the connection pool is racing and multiple connections are being established simultaneously, the connection pool now selects the connection that is ready first, avoiding unnecessary delays and improving overall performance.

Fixed

HTTP server core

  • Backlog size fix: the default backlog size is now 4096 on Linux, and -1 on Windows, *BSD, and macOS. It was -1 on all platforms before this change (which would be equivalent to 0 on Linux).
  • Response body streaming fix: response body streaming is now fixed to avoid buffering instead of streaming, improving performance and memory usage. This also allows Ferron to be used as a reverse proxy for web applications that use SSE (Server-Sent Events). (GitHub issue)

Automatic TLS

  • Automatic renewal info fix: fixed an issue where automatic renewal information was not applied correctly, leading to confusion about certificate expiration.

Ferron 3.0.0-beta.11

Released in August 31, 2026

Added

HTTP server core

  • Support for listening to multiple IP addresses: multiple IP addresses can now be specified in the listen subdirective in tcp block for TCP and QUIC, allowing the server to listen on multiple selected network interfaces.

Forwarded auth

  • Forwarded auth intercept_errors subdirective: added intercept_errors subdirective to forwarded auth stage, allowing custom handling of upstream error responses, similarly to intercept_errors subdirective for reverse proxying.
  • Forwarded auth request_header subdirective: added request_header subdirective to auth_to, allowing headers to be added (+Name), removed (-Name), or replaced (Name) on the request sent to the auth backend, mirroring the reverse proxy’s request_header subdirective.

Changed

Runtime

  • io_uring disabled by default: io_uring is now disabled by default in the runtime configuration, as small static file serving and reverse proxying performance turned out to be slower than with epoll.

Static file serving

  • Symlink check performance optimizations: optimized symlink check performance by caching the results in file open cache along with opened files.

HTTP caching

  • HTTP caching performance optimizations: performed multiple internal throughput optimizations to the in-memory HTTP cache store, especially on the cache hit path.
  • Cache key fingerprinting improvements for observability: truncated cache key (up to 48 characters) with the query string removed, useful for diagnosing why a specific request missed. Optionally with a short non-reversible tag (q=<16 hex chars>), and if the base itself had to be truncated, a second tag (h=<16 hex chars>).
  • Cache metrics improvements: cache decision reasons are now attached to access log entries and cache metrics.

Forwarded auth

  • Forwarded auth X-Real-IP header: added X-Real-IP header to forwarded auth requests, with same value as reverse-proxied requests.

Fixed

Configuration

  • Empty configuration validation: added a check for empty configurations (no ports, no global directives/matchers) to return an error instead of silently starting.
  • Protocol + IP + port host block split fix: previously, the protocol+IP+port combination was not properly split when used as a host block (for example, http 127.0.0.1:18081 lead to two host blocks: http 127 and 0.0.1:18081).

HTTP server core

  • request.uri interpolation fix: previously, request.uri interpolation included the full request URL, including the http/https scheme and hostname. This had been changed to only include the path and query string.
  • HTTP status code for overlong paths: previously, overlong file paths led to 500 Internal Server Error status code to be returned. This has been changed to return 400 Bad Request instead.

Logging

  • Reduced startup logs: removed multiple startup logs for various features to reduce noise in the logs.

Static file serving

  • Static file serving symlink settings fix: previously, symlink mode (follow or not) was not properly respected when serving precompressed static files.

TLS

  • Certificate and private key mismatch fix: previously, when a certificate and private key did not match for a given host, this was silently allowed. This has been changed to error out on the startup of the server.

Ferron 3.0.0-beta.10

Released in August 27, 2026

Breaking changes

HTTP server core

  • Linear-time regular expression engine: The regular expression engine used by Ferron has been replaced by one using a linear-time algorithm, which means that backtracking regex patterns are no longer supported. This change was made to avoid potential ReDoS and catastrophic backtracking issues. If you are using syntax such as (?= ...), (?! ...), (?<= ...), (?<! ...), or \1, you need to rewrite the regexes.
  • Symlink following disable by default for webroots: following symlinks during static file resolution from webroot is now disabled by default for symlink attack prevention. Check if web application uses symlinks (if it uses, use disable_symlinks false or a similar setting).

Automatic TLS

  • DNS provider support reduced: The DNS provider support for automatic TLS DNS-01 challenge has been reduced to a minimal set of providers, to keep Ferron lightweight and lean. Supported providers are rfc2136, route53, bunny, digitalocean, cloudflare, dnsimple, googlecloud, ovh, spaceship, porkbun, and desec.

Configuration

  • enabled true no longer effective: tls { ocsp { enabled true } } and basic_auth { brute_force_protection { enabled true } } are no longer effective and are replaced with tls { ocsp } and basic_auth { brute_force_protection } respectively, to improve consistency across configuration directives.

Added

CLI utilities

  • ferron-passwd --stdin: ferron-passwd can now read passwords from the standard input when not using TTY. Passwords should be one per line.

HTTP server core

  • Unix domain socket listeners (Unix only): added support for listening on Unix sockets for HTTP server, which can be configured using global unix directive. If Unix sockets are configured, Ferron disables TCP and QUIC listeners.
  • Support for h2c listeners: added support for h2c (HTTP/2 cleartext) listeners on top of the existing HTTP/1 and HTTP/2 implementations.

Reverse proxy

  • Same-upstream retries: new max_retries_per_upstream <count> directive controls how many times the proxy retries the same upstream on a transport or connection failure before it falls back to another backend via retry_connection. The retry applies only to idempotent requests with a replayable body and, when retry_budget is enabled, each same-upstream retry consumes a retry-budget token. Default: max_retries_per_upstream 1.

DNS / Automatic TLS

  • command DNS provider (external hook): the new dns-command module provides a command DNS provider for the ACME DNS-01 challenge. It runs an external program for each record change, passing the record details through environment variables (FERRON_DNS_ACTION, FERRON_DNS_DOMAIN, FERRON_DNS_RECORD_TYPE, FERRON_DNS_RECORD_VALUE, FERRON_DNS_RECORD_TTL). The program must exit 0 on success. Configure with provider command and the command directive (with optional min_ttl, default 60). This delegates DNS updates to any DNS server or automation that Ferron does not support natively.

Observability

  • Baggage key promotion for StatsD: the observability-statsd module now supports the baggage directive, which promotes W3C Baggage keys into DogStatsD tags (requires datadog mode). This matches the existing OTLP and Prometheus baggage promotion support.

Changed

HTTP canary

  • Persistent affinity cookie by default: the canary module now writes a persistent affinity cookie (7-day Max-Age, HttpOnly, SameSite=Lax) when set_cookie is enabled, instead of a browser-session cookie. A new cookie block lets you configure the cookie lifetime and attributes (ttl, path, domain, secure, httponly, samesite), matching the HTTP proxy’s affinity cookie settings.

HTTP server core

  • HTTP/3 enabled by default: HTTP/3 is now enabled by default in Ferron, as it’s also no longer declared experimental (it was declared such, because the previous HTTP/3 implementation, h3, was declared experimental as well).
  • Interpolations allowed in static error page paths: interpolations are now supported in static error page paths served by the HTTP server, allowing for using them with A/B testing.

Observability

  • Server and client port in error logs: the client.port and server.port attributes are now included in error log messages, so you can easily identify which client and server ports are involved in a given error.
  • Baggage key promotion max cap more effective: when baggage values exceed the maximum distinct value cap, they are dropped instead of being hashed (cardinality would be the same when hashed, except for hash collisions, so hashing would be ineffective).

Reverse proxy

  • HTTP error resilience: when a backend fails while HTTP request is sent, if the request method is idempotent and body is not sent yet, Ferron will retry the request instead of bailing out.

Fixed

HTTP cache

  • HTTP cache Vary correctness fix: a bug introduced in Ferron 3.0.0-beta.9 led to some header names in Vary header values (If-Match, If-Modified-Since, If-None-Match, If-Range, If-Unmodified-Since, Range) being ignored by the cache, which might lead to wrong 304 responses being served. This has been fixed to properly handle these headers in Vary values.

HTTP server core

  • Configured HTTP responses after HTTP -> HTTPS redirects: HTTP responses configured to be served after the https_redirect stage will now only be served after a successful HTTP-to-HTTPS redirection. This ensures that these responses are not served before the client is redirected, which could lead to unexpected behavior or security issues.
  • Webroots with interpolations in URL rewrites: webroot paths can now contain string interpolations when using with URL rewrite rules.
  • Less QUIC packet loss and retransmissions: previously, QUIC packet loss and retransmissions were higher due to improper routing of packets by the QUIC transport. This has been fixed to improve packet routing (via IP hashing when initializing and connection ID hashing later on) and reduce packet loss.
  • HTTP/3 stream reset errors fixed with curl: fixed curl: (18) HTTP/3 stream 0 reset by server (error 0x0 unknown) on curl for HTTP/3 in underlying HTTP server library used by Ferron. (GitHub issue)
  • IPv4 host blocks never matching: fixed IPv4 host never matching due to IP address canonicalization issues and IPv4-mapped IPv6 addresses.
  • Best practice diagnostics for location blocks: the best practice diagnostics for location blocks with duplicate pathnames are now emitted correctly.

Configuration

  • Stray } fix: earlier, stray } characters in ferron.conf files could cause an infinite loop. These are now properly handled by erroring out.

Admin API

  • Improved config redaction: users subdirective used by HTTP basic auth functionality is now redacted properly in the config JSON output.

TLS

  • Local TLS with ALPN + OCSP: local TLS now supports ALPN and OCSP stapling properly, for HTTP/2 to work properly.
  • OCSP stapling verification fix for some CAs: OCSP stapling verification now works properly for some CAs that have non-standard DER encoding of tbsResponseData (like Actalis).

Observability and tracing

  • High-cardinality metric removed: the ferron.proxy.lb.selections with high-cardinality ferron.proxy.lb.score attribute has been removed to prevent exhaustion of metric storage.

Forward proxy

  • Fixed deny_ips bypass using non-canonical IP representations: previously, deny_ips could be bypassed using non-canonical IP address representations (such as 0x7f.0.0.1 or 2130706433). This has been fixed to normalize the IP addresses first.

Static file serving

  • Ranges beyond the end correctness: the server now returns 416 Range Not Satisfiable when a range request start exceeds the available content length.

Ferron 3.0.0-beta.9

Released in August 16, 2026

Added

CLI

  • ferron directives subcommand: new CLI subcommand that prints every registered configuration directive as structured JSON, grouped by section. Useful for tooling, editor integrations, and inspecting the directive schema of the running binary.
  • FIPS build indication: a build compiled with the fips feature prints This build is configured to use FIPS-certified cryptography. in its version output.

Build & packaging

  • FIPS-certified cryptography builds: the fips Cargo feature enables FIPS-certified cryptography via AWS-LC and rustls. When enabled, OCSP stapling, TLS cipher suites and key exchange groups are restricted to FIPS-approved algorithms, and HTTP basic auth password verification supports only PBKDF2 (Argon2 and scrypt hashes are rejected). You can enable the feature with cargo build --features=fips, or through the build tooling: just build fips=true, cross-build/build.sh --fips, the FIPS=1 Docker build argument, and the FIPS packaging scripts for archive, Debian, RPM, and Windows installer artifacts. FIPS artifacts are named with a +fips suffix (for example, ferron+fips-<version>-<target>.tar.gz), and FIPS Docker images use the -fips tag suffix.
  • SBOMs for release archives: every release now ships a software bill of materials (SBOM) archive next to the binary archives, named ferron-<version>-<target>-sbom.tar.gz (or .zip for Windows). Each archive contains CycloneDX documents in JSON and XML format that list the third-party Rust crates compiled into the binaries. FIPS releases ship matching SBOM archives with the +fips suffix. You can generate SBOMs locally with just package-sbom <target> [fips=true].

Observability & tracing

  • Configuration drift hints: new ferron.admin.config_drift Gauge metric and config_drift / config_drift_hints_enabled fields on the /status Admin API endpoint detect when configuration source files have changed on disk but have not been reloaded. Drift is detected via periodic lightweight mtime comparison (no re-parsing). Enabled by default; disable with drift_hints false in config adapter params (e.g., --config-params "drift_hints=false"). A warn-level log is emitted when drift is detected, and an info-level log when drift resolves after reload.
  • StatsD metrics export: new provider statsd in observability blocks sends Ferron metrics to a StatsD server over UDP, with host, port (default 127.0.0.1:8125), prefix, and datadog directives. The optional datadog true toggle enables DogStatsD extensions: metric tags rendered from metric attributes (and control-plane metadata) and the h histogram type. Without it, histograms are sent as ms timers (with seconds-to-milliseconds conversion). The prefix directive prepends a namespace to every metric name (e.g., myapp.ferron.http.server.request_count:1|c). Each metric event is sent as a separate UDP datagram. (observability-statsd)
  • OTLP metric exemplars: the OTLP observability sink now supports exemplar attachments on metric data points, allowing the last sampled value to be attached as an exemplar. This allows for better trace correlation and debugging by attaching relevant historical context. Previously, this wasn’t supported due to OpenTelemetry SDK limitations, but the limitation was removed by replacing OpenTelemetry SDK with a custom OTLP exporter implementation.
  • OTLP export batching tuning: the logs and traces OTLP signal sub-blocks now accept export_interval <duration> and export_batch_size <number>, and the metrics sub-block accepts read_interval <duration>. This controls how often partially full batches flush and how many finished items trigger an export.
  • OTLP gzip compression: the logs, metrics, and traces OTLP signal sub-blocks now accept gzip [bool] (default false). When enabled, export requests are compressed with gzip (HTTP Content-Encoding: gzip, gRPC gzip compression).
  • OTLP exemplar toggle: the metrics sub-block now accepts exemplars [bool] (default true). Set it to false to stop attaching the last sampled measurement per series as an exemplar.
  • OTLP native histogram toggle: the metrics sub-block now accepts native_histograms [bool] (default true). Set it to false to aggregate histograms with explicit bucket boundaries instead of the exponential layout.
  • Variable interpolation in log filenames: the access_log and error_log directives now support {{variable}} interpolation in file paths. Access log filenames use the accesslog. prefix (e.g. {{accesslog.header_host}}) and resolve against access log event fields. Application log filenames use the log. prefix (e.g. {{log.level}}, {{log.error.type}}) and resolve against log event attributes. Environment variables are also available via the env. prefix. This enables use cases like per-host access logs (access_log /var/log/ferron/{{accesslog.header_host}}/access.log). (GitHub issue)

HTTP server core

  • Variable interpolation in map results: variable interpolations ({{name}}) in map result values are now resolved at runtime.
  • request.uri.query.<param> variables: the request.uri.query.<param> variables are now available for string interpolations, so to not manually create a regular expression for extracting query parameter value from a query string.
  • request.cookie.<name> variables: the request.cookie.<name> variables are now available for string interpolations, so to not manually extract cookie values from the request.
  • CORS origin interpolations: CORS origin values can now be interpolated using {{name}} variable syntax (previously they were declared invalid).
  • Request Cache-Control directives: Ferron now honors the RFC 9111 §5.2.1 request directives on cache lookups. max-age=<n> and min-fresh=<n> revalidate a stored response that does not satisfy the requested freshness window, and only-if-cached returns 504 Gateway Timeout on a miss instead of contacting the origin. no-transform is accepted and has no effect. no-cache (and Pragma: no-cache) already forced revalidation; no-store already bypassed the cache.
  • Canary deployments without a reverse proxy: new canary directive (http-canary) assigns each request a weighted variant with consistent-hash sticky affinity, for canary rollouts and A/B testing of static content. Affinity is ip by default and can be based on a cookie, a request header, or a built-in request variable (hash). With the set_cookie sub-directive, Ferron generates a random sticky key and writes it to the affinity cookie itself when the request has none, so assignments survive client IP changes without help from the web application. Variants are exposed as canary.variant, canary.weight, and canary.key variables, so a document root like root "/srv/www/{{canary.variant}}" serves each variant its own content. Weights and variant lists can change on reload; clients near variant boundaries may move, other clients keep their variant. Observability includes the ferron.canary.requests counter (with ferron.canary.name and ferron.canary.variant attributes), span attributes on the ferron.stage.canary span, and the ferron.canary.variant custom access log field. (http-canary)
  • HTTP/3 protocol configuration directives: added several new h3_* subdirectives in http directive for fine-tuning HTTP/3 protocol configuration.

HTTP caching

  • On-disk cache persistence: the HTTP cache can now survive process restarts. Set persist <dir> in a cache block to write cache mutations to a journal and periodic snapshots under <dir>/<zone>. On startup, Ferron replays the snapshot and journal back into memory. persist_interval <duration> (default 30s, minimum 1s) controls how often Ferron flushes queued mutations. By default only public entries are persisted; persist_private opts into persisting private entries. Deletions are always persisted. The persistence directory is single-process only — do not share it between instances. A SIGHUP config reload does not touch the on-disk cache.

Changed

HTTP server core

  • PGO for GNU/Linux and some Linux with musl: PGO (profiled-guided optimization) is now enabled for GNU/Linux targets and some Linux targets with musl libc (64-bit x86, ARM64), for pre-built Ferron binaries. This mainly improves tail (p90/p99/max) latency for web requests.
  • .ferron file extension: .ferron files are now supported as an alternative to .conf (which is a generic file extension) files for configuration (GitHub issue).
  • Panic hook improvements: panic hook has been simplified (removing backtraces that are likely unhelpful), and it now logs the Ferron version and build target.
  • HTTP/2 and HTTP/3 implementations: HTTP/2 and HTTP/3 functionality in Ferron now depends on vibeio-http’s in-house protocol implementations instead of h2 and h3 from Hyperium. New HTTP/2 implementation features higher space savings for headers (~95%, versus ~91% with the previous implementation for a “Hello World” application), while new HTTP/3 implementation features improved protocol compliance (47/49 on h3spec, versus 44/49 with the previous implementation).
  • QUIC performance optimizations: QUIC “endpoints” are now per-thread instead of single-threaded, improving throughput and tail (p90, p99) latency for HTTP/3 requests.

Observability & tracing

  • Non-existent directory handling for log files: when a log file path contains a non-existent parent directory, Ferron now creates it automatically before writing to the log file.

Configuration validation

  • Diagnostic span location improvements: diagnostic span location in error messages is now more accurate, showing the exact line and column where the error occurred in the configuration file.

Container images

  • “Slim” Debian-based Docker images: ferronserver/ferron:3-debian (and other Debian-base image tags) now are based on a “slim” variant of Debian instead of the full Debian image, making the image smaller and removing unnecessary packages. The slim variant is still compatible with the full Debian image, so it should not break existing deployments.

Access control

  • Interpolated strings as password hashes (HTTP basic auth): interpolated strings can now be used as password hashes for HTTP basic authentication (for example when moving from a static password to an environment variable).
  • Password hashing backends for HTTP basic auth: password verification no longer depends on the password-auth crate. PBKDF2 verification now uses aws-lc-rs, scrypt uses AWS-LC (EVP_PBE_scrypt), and Argon2 uses argon2-rs (the Argon2 C reference implementation) with constant-time comparison. Newly supported hash formats: $pbkdf2-sha384$ and $pbkdf2-sha512$. Base64 salt and hash fields now accept both padded and unpadded encodings. PBKDF2 hashes accept a bare iteration count ($pbkdf2-sha256$600000$...) or the PHC parameter form ($pbkdf2-sha256$i=600000,l=32$...).

Fixed

HTTP server core

  • Trailing slash redirects: trailing slash redirection logic now correctly handle index files (e.g., index.html) and redirects to the correct URL (with trailing slash) rather than serving two URLs (both with and without trailing slash)
  • Connection accept fix for poll: previously, connections at TCP listeners (including HTTP/1.x and HTTP/2) accepted only a single connection when using poll (not epoll), stalling afterwards. This has been fixed to be able to accept multiple connections (see vibeio changelog).
  • HTTP/2 host header handling correctness: previously, if both host and :authority HTTP/2 headers were present, the web server would return a 400 error, which might not be correct according to the HTTP/2 specification (RFC 9113, section 8.3.1). This has been fixed to override Host header value with :authority header value instead of appending a new value.
  • Symlink ownership check: previously, symlink ownership check (disable_symlinks if_not_owner) was effectively a stub that effectively disabled all symlinks. This has been replaced with a proper implementation.
  • Conflicting Alt-Svc headers for HTTP/3: the server now rewrites Alt-Svc header values to remove conflicting HTTP/3 port definitions.

HTTP caching

  • RFC 9111 compliance: updated freshness lifetime precedence, 304 revalidation header merging, must-revalidate handling, no-cache="field-names" support, and 206 Partial Content cacheability to align with RFC 9111.
  • Cache key & scope: cache keys now use resolved vhosts, normalize whitespace/sorting in Vary values, and exclude client IPs for private responses. Added cardinality bounds for private keys and query string stripping from fingerprints to prevent cache fragmentation and information leakage.
  • Purge & security: scoped PURGE requests to requesting hosts, added constant-time X-Purge-Secret authentication for propagated purges, and ensured Set-Cookie headers are never stored verbatim.
  • SWR & performance: improved stale-while-revalidate with SWR leader fresh response delivery, singleflight coalescing with timeouts, and throttled expired-entry sweeps.
  • Request handling: no-store requests now serve fresh entries (but don’t store), hop-by-hop headers are stripped, and fresh hits now answer client conditionals (e.g., If-None-Match) with local 304s.
  • Metrics & diagnostics: corrected Cache-Status labels for stale-if-error and fixed double-counting in cache request metrics.

Observability

  • OTLP gRPC no_verification TLS handshake: previously, when an OTLP gRPC endpoint was configured with no_verification true and used HTTPS, TLS handshakes failed because the custom certificate verifier rejected TLS 1.2/1.3 handshake signatures. This has been fixed to return assertion-based verification results (matching the HTTP exporter behavior).
  • ferron.ocsp.stapling.hit_total metric emission fix: previously, the ferron.ocsp.stapling.hit_total metric emission didn’t function at all. It has been fixed to emit the metric to global observability sinks correctly.
  • ACME TLS resolution errors: previously, TLS resolution errors were always logged into the console when using automatic TLS via ACME. This has been changed to log them into configured observability sinks.
  • Spurious abrupt connection termination error logs: earlier, some abrupt connections termination log were logged (Reverse proxy: HTTP upgrade tunneling failed: peer closed connection without sending TLS close_notify: https://docs.rs/rustls/latest/rustls/manual/_03_howto/index.html#unexpected-eof), even if the connection was idle. This has been fixed along with an update to the HTTP server library used by Ferron (see vibeio-http changelog).

Configuration validation

  • JSON configuration parse error reporting: previously, configuration parse errors were reported using human-readable error messages (even if the --json flag was used). This has been fixed to report parse errors in JSON when configured to do so.

Reverse proxy

  • Config reload cleanup: on configuration reload, old reverse proxy health check probe tasks are now aborted and per-config caches (resolved upstreams, retry budgets, unhealthy backend counters) are invalidated. Previously these accumulated on every reload, leaking background tasks and memory.
  • Upstream resolution cache fix - in earlier versions of Ferron 3 beta, stale upstream resolution cache (with infinite TTL) could have caused upstream connection errors, due to drift between cached state and actual DNS state. This has been fixed by removing the infinite-TTL internal upstream resolution cache.
  • X-Forwarded-For and Forwarded header value fix: previously, X-Forwarded-For and Forwarded header values were truncated to 256 bytes maximum, which could lead to incorrect (or even malformed) client IP values in the proxy request. This has been fixed to set the header values correctly without truncation.

Static file serving

  • Range request correctness: previously, weak ETags were included with 206 Partial Content responses (and some other responses with Range request header), which is a violation of HTTP spec (RFC 7232, RFC 7233). This has been fixed to remove ETags from responses to range requests.

CORS

  • CORS Vary header correctness - previously, if response Vary header was set upstream, it would be overwritten by Vary: origin header, which might lead to incorrect caching. This has been fixed to append origin to the list of header names in Vary header value instead.
  • CORS Vary: Origin header: the Vary: Origin header is now always added to CORS responses (instead of selectively).
  • CORS Origin request header fix: previously, if cors directive contained non-* origin, Ferron didn’t add CORS headers at all. This has been fixed to add CORS headers for non-* origins as well.

CLI utilities

  • ferron-fmt duration string formatting fix: previously, ferron-fmt formatted short duration strings (like 1s) without quotes, which caused subsequent parse errors. This has been fixed to always quote this kind of strings.

Ferron 3.0.0-beta.8

Released in July 15, 2026

Breaking changes

Configuration format

  • Invalid escape sequences in quoted strings are now parse errors — previously, unknown escape sequences (e.g., \z, \$) were silently passed through. They now produce a lexer error. Use raw string literals (r"...") if you need literal backslashes in values (e.g., regex patterns).

Added

Configuration format

  • Raw string literals — new r"..." syntax for strings with no escape processing. Raw strings are ideal for regex patterns where backslashes should be treated literally (e.g., r"^/api/v1(?:/|$)" instead of "^/api/v1(?:/|$)"). Raw strings do not support interpolation.
  • Line continuation — a backslash (\) at the end of a line joins it with the next line, allowing long directives to be split across multiple lines (e.g., proxy http://localhost:3000 \ followed by http://localhost:3001).
  • Semicolons as optional delimiters — semicolons (;) can now appear between statements and between host patterns as optional delimiters, providing compatibility with tools that generate semicolon-terminated output.

Changed

Configuration format

  • Idiomatic configuration style — documentation now uses the new ferronconf idiomatic style: bare strings without quotes, bare boolean flags (e.g., io_uring instead of io_uring true), raw string literals for regex patterns (r"..."), and consistent 4-space indentation.
  • Expanded bare string character set — bare strings now accept ., :, *, -, +, /, %, &, ?, @ directly without quoting, reducing the need for quoted strings in directive values.

Default pages

  • Redesigned default error and directory listing pages — the built-in error pages and static directory listings have been redesigned to match new Ferron 3 branding (see https://ferron.sh/blog/ferron-new-look). The installation landing page (wwwroot) has also been refreshed to match.

Fixed

Configuration validation

  • Duplicate observability block warning fix — fixed duplicate observability block warnings (they’re now only displayed once) for ferron doctor.

Ferron 3.0.0-beta.7

Released in July 11, 2026

Added

Observability & tracing

  • Prometheus native histogram support — the Prometheus observability backend now supports OpenMetrics native histograms alongside classic bucket histograms. Enable with endpoint_native_histograms true in the observability.prometheus block. Native histograms are only available in protobuf format; the text format continues to expose classic bucket histograms. Note that native histograms and exemplars are mutually exclusive for histograms — when native histograms are enabled, histogram exemplars are disabled.
  • Prometheus metric exemplars — the Prometheus observability backend now emits OpenMetrics exemplars on counter and histogram metrics when request trace context is available. Each exemplar carries the trace_id and span_id of the request that triggered the observation, enabling direct correlation between metric spikes and distributed trace flame graphs in compatible observability backends (Grafana, Prometheus 2.23+). Exemplars are enabled by default for counters. For histograms, exemplars are active only when endpoint_native_histograms is false (the default), since native histograms and exemplars are mutually exclusive.
  • Configuration content hash — new ferron.admin.config_mtime Gauge metric and config_file_hash / config_file_mtime fields on the /status Admin API endpoint expose the xxh3 content hash and last-modified time of the loaded configuration files. This enables rapid cluster-wide drift detection via a single PromQL query checking for hash mismatch across instances.
  • DNS cache TTL remaining gauge — new ferron.proxy.dns.cache_ttl_remaining_seconds Gauge metric exposes aggregated remaining TTL for DNS cache entries (min, max, avg aggregation via aggregation attribute). A companion ferron.proxy.dns.cache_entries Gauge tracks the number of active cache entries. This allows operators to alert on impending DNS record expiration before backend connections break.
  • Pool connection limit metrics — the ferron.proxy.pool.local_limit and ferron.proxy.pool.global_limit pool metrics are now available for monitoring connection pool usage.
  • Method and idempotency labels on retry metrics — the ferron.proxy.retry.count Counter and ferron.proxy.retry.final Gauge now include http.request.method (categorized: GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS, CONNECT, TRACE, _other) and ferron.proxy.method_idempotent (bool per RFC 9110 §9.2.2) labels, enabling zero-delay alerting on non-idempotent retry anomalies that indicate potential state corruption.
  • Selected backends per request metric — the ferron.proxy.backends.selected_per_request Counter metric is now emitted for every proxied request, with backend_url and backend_unix_path attributes. This allows operators to see traffic amplification effects when multiple backends are selected for a single request.

Security

  • Rate limit throttling — the rate_limit directive now supports a throttle subdirective that allows rate-limited requests to be delayed instead of rejected. When enabled, requests exceeding the configured rate limit are queued before being processed. This reduces the likelihood of client-side errors and improves user experience during temporary traffic spikes.

Reverse proxying

  • Token-bucket retry budget — the new retry_budget directive implements a token-bucket-based retry budget that limits retries to a configurable fraction of steady-state traffic. When enabled alongside retry_connection true, retries consume tokens from a shared pool that is replenished by successful requests. If the retry budget is exhausted, further retries are refused and the request immediately returns 503 Service Unavailable, preventing cascading retry storms from amplifying backend failures into a self-inflicted DDoS. Supports max_retry_rate (default: 10%), max_tokens (default: 10), and refill_rate (default: 2.0 tokens/sec) subdirectives. New metrics: ferron.proxy.retry.budget_exhausted (Counter) and ferron.proxy.retry.budget_tokens_available (Gauge).

HTTP server core

  • JSON error responses — the new json_errors directive in the http-jsonerror module enables structured JSON error responses for HTTP 4xx/5xx errors. Supports both RFC 9457 application/problem+json (with type, title, status, detail, and optional trace_id fields) and simple application/json output formats. The type URI is configurable (default: about:blank), with support for {status} placeholder substitution (e.g., https://http.dev/{status}). Use location /api { json_errors true } to scope JSON errors to REST API endpoints. (http-jsonerror)
  • Client certificate common name variable — the mtls.cn variable is now available for interpolations in the server configuration, allowing to use the client certificate’s common name in routing rules and conditionals.
  • HTTP error page placeholders — custom error page files now support optional placeholder substitution. Enable with error_page_placeholders true; the {{trace.id}} and {{trace.spanid}} placeholders are then replaced with the current request’s trace ID and span ID before the page is served. Substitution reads the file into memory, bypassing the zerocopy/sendfile optimization for those responses.
  • Static file resolution tracing — the file resolution process now emits a dedicated ferron.pipeline.file_resolve trace span with ferron.file_resolve.request_path, ferron.file_resolve.root_path, ferron.file_resolve.outcome, and ferron.file_resolve.resolved_path (success) or ferron.file_resolve.last_candidate_path (error) attributes. Resolution error paths (403 Forbidden, 400 Bad Request) now include ferron.file_resolve.* fields in access logs, replacing the previous “black box” behavior where the file resolution process produced no observability signals. Directory listings now emit ferron.static.dir_path trace span and access log attributes, and contribute to the ferron.static.responses counter metric with ferron.static.outcome values "listing", "listing_disabled", "method_not_allowed", or "options".

Changed

Default pages

  • Redesigned default error and directory listing pages — the built-in error pages and static directory listings have been redesigned with a modern, minimal, neutral style (system fonts, subtle accent gradient, responsive layout, and automatic light/dark mode). All styling remains inlined with no external assets. Default error pages now display the request’s trace ID when trace context is available, improving debuggability. The installation landing page (wwwroot) has also been refreshed to match.

HTTP caching

  • Unified cache eviction log summaries — the cache eviction log summaries are now unified to not include eviction reasons (they’re already in log attributes).

Observability & tracing

  • Prometheus text format content-type — the Prometheus text endpoint now serves application/openmetrics-text; version=1.0.0 instead of text/plain; version=0.0.4, matching the OpenMetrics specification used by prometheus-client. Prometheus scrapers that do not support OpenMetrics content negotiation may need to be updated.
  • Native exponential histograms for OTLP latency metrics — all OTLP histogram metrics now use Base2 Exponential Histograms instead of fixed linear buckets. This provides high-resolution percentile data across deep orders of magnitude (1ms to 100s), eliminating the need for manual bucket allocation and preserving tail-latency outliers that were previously masked by coarse bucketing. Affected metrics include ferron.proxy.upstream.duration, ferron.proxy.pool.wait_time, ferron.proxy.tls.handshake_time, ferron.proxy.connect.latency, ferron.proxy.ttfb, http.server.request.duration, and all other histogram instruments exported via OTLP.
  • QUIC client/server address logging — QUIC connection error logs now include the client and server IP addresses, making it easier to trace connections and identify potential bottlenecks.
  • stage.name in traces renamed to ferron.stage.name — renamed stage.name to ferron.stage.name traces to ensure consistency with other Ferron-specific attributes.

Reverse proxying

  • DNS thundering herd prevention — the DNS resolver now uses a singleflight mechanism to prevent multiple concurrent DNS queries for the same hostname, reducing load on DNS servers and improving performance.
  • DNS resolution status metric attribute — the metrics_resolved_ip directive now also emits a ferron.proxy.dns_status attribute on all proxy metrics and access logs when enabled. Values: "resolved" (DNS succeeded), "nxdomain" (domain not found), "dns_error" (other DNS failure), "logical_dns" (resolution deferred to connect time), "static" (DNS not applicable). This provides visibility into DNS resolution outcomes without requiring per-IP label explosion.

Fixed

HTTP caching

  • Duplicate eviction metrics — eviction metrics (and logs) were being emitted twice for cache hits, leading to incorrect metric values. This has been fixed.

Observability & tracing

  • Prometheus endpoint is no longer on-demand — the Prometheus endpoint is now always available on the configured port, regardless of the number of active connections.
  • Reverse proxy metric naming consistency — the ferron.proxy.backend.excluded metric name has been renamed to ferron.proxy.backends.excluded for consistency with other reverse proxy metrics.

Ferron 3.0.0-beta.6

Released in July 8, 2026

Added

Security

  • Admin API bearer token authentication — the admin block now supports an auth_token directive for bearer token authentication on all admin API endpoints except /health. Clients must send Authorization: Bearer <token> header. The /health endpoint is exempt to allow load balancer and orchestrator probes.
  • Prometheus endpoint bearer token authentication — the observability.prometheus block now supports an endpoint_auth_token directive for bearer token authentication on the /metrics scrape endpoint. Scrapers must send Authorization: Bearer <token> header.

Networking

  • Multipath TCP (MPTCP) support — the tcp block now supports a multipath directive that enables Multipath TCP on the HTTP listener. MPTCP allows a single TCP connection to use multiple network interfaces simultaneously, improving throughput and connection resilience. When enabled, Ferron attempts to create an MPTCP socket; if the kernel does not support MPTCP or it is disabled, a warning is logged and the listener falls back to standard TCP. (GitHub issue)

Observability & tracing

  • Admin API request metrics — the admin API now emits per-request metrics (ferron.admin.request.duration, ferron.admin.request.count) for all endpoints except /health, with http.request.method, url.path, and http.response.status_code attributes. A dedicated ferron.admin.reload.count counter tracks reload attempts.
  • Admin API structured logs — the admin API now emits structured log events through the observability pipeline for config reloads (Info/Error) and config queries (Info). Health checks and status queries are not logged to avoid noise.
  • Prometheus scrape self-metrics — the Prometheus endpoint now emits ferron_prometheus_scrape_duration_seconds (Histogram), ferron_prometheus_scrape_total (Counter), and ferron_prometheus_scrape_errors_total (Counter) for monitoring scrape performance.
  • Process identity in OTel resources — the OTLP resource now automatically includes process.pid and process.start_time attributes, allowing observability backends to distinguish between concurrent and sequential process lifetimes. This prevents cumulative counters from adjacent process lifetimes from being visually interleaved in dashboards.
  • Status-code labels on cache store counters — the ferron.cache.stores metric now includes an http.response.status_code attribute, allowing operators to directly query whether error responses are entering the cache layer without correlating timing across separate request and error series.
  • Cache key fingerprint in access logs — the cache module now contributes a ferron.cache.key_fingerprint field to HTTP access log lines, containing a truncated representation of the cache key. This simplifies debugging why a specific request resulted in a cache miss.
  • Singleflight coalescing traceability in access logs — the cache module now contributes ferron.cache.coalesced (bool) and ferron.cache.coalesce_wait_duration_ms (float) fields to HTTP access log lines, providing direct visibility into how long client requests are queuing inside the proxy during concurrent upstream revalidation.
  • Cache key components in OTel trace attributes — the ferron.stage.cache span now includes ferron.cache.key.uri, ferron.cache.key.method, and ferron.cache.key.evaluated_cookies attributes, making trace flame graphs instantly diagnostic when evaluating hit-rate degradation caused by high-cardinality metadata variations.
  • Singleflight coordination metrics — new ferron.cache.coalesced_requests (Counter) and ferron.cache.singleflight_active_locks (Gauge) metrics expose the efficiency of the request deduplication layer, providing visibility into how effectively singleflight handles concurrent thundering herds.
  • Eviction reason structured logs — cache eviction events now emit debug-level structured log entries with eviction.reason (ttl_expired or capacity_reached_lru), eviction.count, and ferron.cache.zone attributes, providing precise eviction timelines alongside the existing cumulative metrics.
  • Cross-plane traceability — the new control_plane directive allows embedding arbitrary key-value metadata and static OpenTelemetry span links in the server configuration at global, host, or location levels. Metadata values are automatically included as ferron.control_plane.* attributes on all observability signals (OTLP traces, logs, metrics, access logs) and as [key=value] prefixes in console and file logs. Span links establish causal relationships between control plane events and data plane traces without requiring a parent-child relationship. The most specific level wins (location > host > global), and metadata values support variable interpolation.

HTTP caching

  • vary_cookies directive — the cache module now supports a vary_cookies directive that accepts an explicit list of cookie names for cache key differentiation. When set, only the listed cookies are included in the cache key, preventing high-entropy tracking or session cookies from fragmenting the cache and collapsing hit rates.

Changed

Reverse proxy

  • localhost resolution in reverse proxylocalhost is no longer exempt from STRICT_DNS-style DNS resolution; it is treated as a regular hostname and resolved via A and AAAA records.

Fixed

Reverse proxy

  • Circuit breaker recording fix — recent failures are now recorded correctly in the circuit breaker state, allowing circuit breaker logic to function as expected.

Ferron 3.0.0-beta.5

Released in July 5, 2026

Added

Security

  • Configurable symlink traversal protection — the new disable_symlinks directive allows configuration of symlink handling during static file serving (false, true, or if_not_owner on Unix). When enabled, symlinks are detected without following them during path traversal, preventing symlink-based escape attacks from outside the configured webroot. Defaults to false for backward compatibility.
  • Error rate threshold — the abuse_protection directive now supports an error_rate_threshold sub-directive that monitors HTTP response status codes. When a client triggers an abnormal number of configured error responses (e.g., 404, 403) within a time window, their IP is temporarily banned. This detects hostile scanning behavior such as probing for old vulnerabilities or non-existent plugin paths.

Observability & tracing

  • Per-host TLS handshake metrics — new per-host metrics for TLS handshake duration (ferron.tls.handshake.duration histogram), handshake count (ferron.tls.handshake.total counter), and active connections (ferron.tls.connections.active up-down counter). Each metric carries ferron.host, tls.protocol.version, and tls.cipher_suite attributes, enabling per-host visibility into TLS performance and client compatibility.
  • TLS attributes on request traces — the ferron.request root span now includes tls.protocol.version and tls.cipher_suite attributes for HTTPS connections, correlating TLS parameters with individual requests without requiring a separate TLS span.
  • Per-host OCSP metrics — OCSP fetching metrics (ferron.ocsp.fetches_total, ferron.ocsp.fetch_duration_seconds) now include a ferron.host dimension, and a new ferron.ocsp.stapling.hit_total counter tracks per-request OCSP stapling outcomes per host.
  • Client and server IPs in error logs — structured error logs for bad requests (400), request timeouts (408), TLS handshake failures, and TCP connection errors now include client.address and server.address attributes, enabling IP-based correlation and troubleshooting.
  • Load-balancer selection score metric — new ferron.proxy.lb.score Gauge metric exposes the combined selection score used by the two_random and p2c_ewma load-balancing algorithms. For P2C-based algorithms, the two candidate scores compared during selection are captured and the winner’s score is emitted per request. Lower scores indicate more preferred backends, enabling direct visibility into traffic distribution without manual TTFB correlation.
  • Upstream response truncation detection — new ferron.proxy.upstream.response_truncated Counter metric and structured warning log detect when an upstream backend closes the response stream before the declared Content-Length bytes have been forwarded. The proxy tracks bytes received during body streaming and flags truncation when the stream ends prematurely. A structured warning log includes the backend URL, bytes received, and expected content length, enabling immediate visibility into backend issues that return partial responses with valid HTTP status codes. This does not capture response bodies; only the byte count and content length are tracked.
  • Latency-aware circuit breaker — the circuit_breaker directive now supports an optional latency_threshold sub-directive. When configured, upstream responses exceeding this duration count as failures toward tripping the circuit, alongside transport failures and (optionally) 5xx responses. This allows circuit breakers to eject pathologically slow backends that return successful status codes.
  • Circuit breaker flapping detection — the circuit_breaker directive now supports flapping_transitions and flapping_window sub-directives for detecting rapidly oscillating upstream backends. When an upstream transitions between circuit breaker states more than flapping_transitions times (default: 3) within flapping_window (default: 10s), a single warning log is emitted and the ferron.proxy.circuit.flapping Gauge metric is set to 1. Individual transition logs are suppressed while the upstream is flapping. When transitions stabilize, a recovery log is emitted and the metric resets to 0.
  • Circuit breaker slow-start — the circuit_breaker directive now supports a slow_start duration sub-directive. When a backend transitions from half-open to closed (recovers), the load balancer applies a temporary virtual connection penalty that linearly decays over the configured duration (e.g., slow_start 10s), preventing thundering herd by preventing the recovered backend from being immediately overwhelmed with new connections.
  • Configurable metric cardinality control — the metrics_resolved_ip directive (default: false) controls whether ferron.proxy.backend_resolved_ip is included in proxy metrics attributes. When disabled, metrics identify backends only by their configured URL, keeping cardinality low for environments with ephemeral IP addresses (e.g., Kubernetes). When enabled, each resolved IP becomes a distinct metric label value. This replaces the previous behavior of always emitting resolved IPs, which could cause cardinality explosion.

Reverse proxy

  • Weighted load balancing for all algorithmsrandom, two_random, and p2c_ewma now support per-upstream weight directives for proportional traffic distribution, joining round_robin and least_conn. Higher weight values receive proportionally more requests across all five load balancing algorithms and session affinity.
  • Priority-based failover — backends can now be assigned numeric priority values to implement tiered failover. Lower values indicate higher priority. When all backends in a tier are unavailable, the next tier is used as a fallback. SRV upstreams support priority via an additive offset applied to DNS SRV priorities.
  • SRV native weight support — DNS SRV record weights are now used for weighted load balancing within priority tiers. Each backend’s effective weight is dns_weight × config_weight. The config weight subdirective acts as a multiplier on top of DNS weights.
  • Strict DNS (A/AAAA) resolution — static upstreams with hostnames now resolve A/AAAA records via Hickory DNS by default, creating a separate backend per resolved IP for per-IP load balancing, circuit breaking, and health checking. IP literals, localhost, Unix sockets, and logical_dns true upstreams are unaffected. Use dns_servers to specify custom resolver IPs.
  • DNS result caching — resolved DNS results for both strict DNS (A/AAAA) and SRV upstreams are cached in memory with TTL-based expiry derived from the DNS response TTL. This avoids redundant DNS resolution and lock contention for high-traffic hostnames. Cache entries expire based on the minimum TTL from the DNS response records, with a 30-second fallback when no TTL is available.
  • HTTP upstream connection timeout — the connection_timeout subdirective is now available for all upstreams, allowing configuration of the maximum time to establish a TCP connection to a backend. This prevents long hangs when backends are unreachable or slow to accept connections.
  • Health check logging — health check initialization logs now include the HTTP method and URI in the log output, making it easier to debug and understand which health checks are being performed.
  • Upstream circuit transition logging — circuit breaker state transitions (open/half-open/closed) now log the upstream address and duration of the open circuit, making it easier to diagnose and troubleshoot circuit breaker behavior.

Observability & tracing

  • Per-stage span attributes — all HTTP pipeline stages now emit stage-specific span attributes on their ferron.stage.<name> spans, enabling detailed flame graph analysis without requiring metric export. Each module’s per-stage span carries module-specific context such as upstream backend URLs, cache results, rate limit decisions, authentication outcomes, and response status codes. See each module’s documentation for the full list of attributes and the tracing reference.
  • Upstream state in client traces — the ferron.stage.reverse_proxy span now includes upstream runtime state attributes (ferron.proxy.upstream.circuit_state, ferron.proxy.upstream.is_flapping, ferron.proxy.upstream.health_status, ferron.proxy.upstream.consecutive_failures, ferron.proxy.upstream.active_connections) correlating the selected backend’s health, circuit breaker, and connection state with individual client requests for OTLP trace debugging.
  • Resolved IP address attributes — reverse proxy metrics now include resolved IP addresses when strict DNS (A/AAAA) resolution is enabled. The connect_to field of UpstreamInner is exposed as ferron.proxy.backend_resolved_ip metric attribute for all backend-related metrics, allowing observability of the actual IP addresses backends are connected to. This complements existing backend_url and backend_unix_path attributes.
  • Module-injected access log fields — all HTTP pipeline modules now emit debuggability attributes directly in access log lines, not just as trace span attributes. Proxy requests include ferron.proxy.backend_url, ferron.proxy.connection_reused, and ferron.proxy.retry_count. Cache results include ferron.cache.result and ferron.cache.zone. Rate limit decisions include ferron.ratelimit.result and ferron.ratelimit.zone. Auth outcomes include ferron.basicauth.result and ferron.fauth.result. Abuse protection includes ferron.abuseban.action. Static files include ferron.static.file_path. CGI/FastCGI/SCGI backends include their respective backend_url and script_path fields. See the logging reference for the full list.
  • Circuit breaker state in access logs — access log lines for proxied requests now include ferron.proxy.circuit_breaker_state (closed, open, or half_open), and the ferron.proxy.circuit.state gauge is now emitted on every proxied request instead of only on state transitions. This ensures circuit breaker visibility in both log queries and metric dashboards.

Changed

Static file serving

  • FD-based metadata and file handle reuse — static file metadata is now obtained via fstat-like system calls instead of a separate stat-like calls, closing the TOCTOU window between path validation and metadata read. A per-thread file descriptor reuse pool with preemptive bulk expired removal eviction is implemented for handle reuse, improving performance. HttpFileContext now carries file: ReusedFile instead of metadata: Metadata, so all consumers derive metadata from the validated file handle.
  • Path resolution without canonicalization — static file path resolution now uses component-level validation with simple PathBuf checks instead of costly filesystem canonicalization. This improves performance, eliminates path canonicalization overhead, and reduces the TOCTOU window by validating paths before symlink traversal checks. Combined with disable_symlinks, this implements nginx-style defense-in-depth protection.

Reverse proxy

  • Circuit breakers enabled by default — the circuit_breaker directive is now enabled by default for all proxy configurations. Circuit breakers track transport failures (TCP connect errors, TLS errors) and optionally upstream 5xx responses (when record_5xx true), then temporarily eject unstable backends from the load balancer. This change improves resilience by protecting against cascading failures without requiring explicit configuration. To disable circuit breakers, set circuit_breaker false.
  • ferron.proxy.requests metric with backend URL — the ferron.proxy.requests counter metric now includes a backend URL (and related) attribute for all upstream requests, allowing observability of which backend URL handled each request.

Observability

  • Canonical IP addresses in logs and traces - client_ip_canonical and server_ip_canonical are now available as OpenTelemetry-style log attributes and trace span attributes, replacing the legacy client_ip/server_ip.
  • Custom log fields - custom log fields that have . in their names are used as-is in OTLP logs instead of being converted to _ in the log attribute name. This allows for more flexible log field naming and avoids conflicts with existing attribute names.

Fixed

Observability

  • Consistent log targets - tls-acme and tls-http now use ferron-tls-acme and ferron-tls-http log targets respectively instead of ferron_tls_acme and ferron_tls_http.
  • Circuit breaker gauge value fix - the ferron.proxy.circuit.state gauge now correctly uses 1 for open and 2 for half_open, matching the constant definitions. Previously these values were swapped.

Ferron 3.0.0-beta.4

Released in June 28, 2026

Added

HTTP caching

  • Cache zones — the cache directive now supports a zone subdirective at host scope, allowing multiple hostnames to share a single in-memory cache store. Named zones can be pre-configured at global scope with custom max_entries capacity. When a global cache { max_entries } block exists without explicit zone blocks, all hosts share a global zone by default. Hosts can opt out with an explicit zone directive.
  • Generation-aware cache capacitymax_entries is 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 a ferron.cache.zone attribute identifying the zone the request belongs to.

Rate limiting

  • Rate limit zones — the rate_limit directive now supports a zone subdirective 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 global rate_limit block exists without explicit zone blocks, all hosts share a global zone by default. Hosts can opt out with their own rate_limit block.
  • 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 a ferron.ratelimit.zone attribute identifying the zone the request belongs to.

Static file serving

  • If-Range support — The If-Range header 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 Content with the available range instead of 416 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 bytes prefix in Content-Range part headers (per RFC 7233 §4.1) and correctly serve one additional byte per range to match inclusive-to-exclusive bounds (is_end_stream no longer signals end-of-stream while the last range’s data is still being streamed).
  • If-None-Match POST fix — POST requests with a matching If-None-Match header now correctly return 412 Precondition Failed instead of 200 OK (per RFC 7232 §4.2).
  • If-Match: * with non-GET/HEAD fixIf-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 Range headers are now treated as absent (returning 200 OK) instead of 416 Range Not Satisfiable, per RFC 7233 §3.1.
  • file_cache_control panic fix — Malformed file_cache_control config values can no longer panic the request handler; a validation warning now catches invalid characters.
  • Missing Content-Length for compressed responses — Non-identity (compressed and precompressed) file responses now include the correct Content-Length header.
  • bytes_sent metric fix — The bytes_sent metric 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.
  • Expires header in past edge case fix - a response with a past Expires header and no Cache-Control directives 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-revalidate is 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 \0 as the separator instead of & in the cache key.
  • 304 revalidation TTL fix - when a cached response is revalidated via a 304 Not Modified response, the stored response’s TTL, stale-while-revalidate, stale-if-error, and must-revalidate directives are now updated from the 304 response’s Cache-Control headers per RFC 9111 §4.3.4. Previously only ETag and Last-Modified validators were updated, causing the original TTL to persist even when the upstream sent a different max-age.
  • Cache variant metadata leak fix - the variants_by_base map 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.

Ferron 3.0.0-beta.3

Released in June 26, 2026

Breaking changes

If you are upgrading to this beta version, you must update your configuration files to accommodate the following syntax refactors:

  • Removed force_trace directive - the force_trace directive has been removed as trace context creation is now always enabled for every request. Configurations using force_trace true should remove the directive; configurations using force_trace false should also remove it since the behavior now matches the default.
  • Default text log format changed - the default text access log format has been changed from Combined Log Format to Enhanced Combined Log Format, which adds Host header and trace ID fields. If you rely on the default text log format and want to keep the previous behavior, explicitly set the pattern to the Combined Log Format using access_pattern "%client_ip - %auth_user [%t] \"%method %path_and_query %version\" %status %content_length \"%{Referer}i\" \"%{User-Agent}i\"" in your log block.
  • Circuit breaker 5xx responses no longer counted by default - upstream 5xx responses no longer trip the circuit breaker unless record_5xx true is explicitly set. This makes the circuit breaker purely transport-failure-based by default, giving operators explicit control over Layer 7 error sensitivity.
  • passive_check directive removed - the passive_check directive has been replaced with a more generic circuit_breaker directive that supports passive health checking and circuit breaking for both HTTP and gRPC services.

Added

Modules

  • http-variables - a new module for setting interpolation variables based on request conditions and mapping variables to custom access log fields.

Security

  • Custom abuse event registration - it’s now possible to register custom abuse events, for example, for honeypots.

Observability & metrics

  • Reload metrics and logs - the new metrics-reload module emits application log events and a ferron.reloads counter metric around configuration reloads, with a ferron.reload.successful attribute indicating success or failure and an error.message attribute on failures.
  • Abuse ban logging - the abuse ban logger now logs at the WARN level when an IP is triggered for banning, including the banned IP address and reason.

HTTP server core

  • set_var directive - new directive for setting interpolation variables based on regex matching against request attributes, with optional value, case_insensitive, and negate subdirectives. (http-variables)
  • log_field directive - new directive for mapping any resolvable variable (including interpolated strings) to custom access log field names, evaluated after the response is generated. (http-variables)
  • Support for date-based caching - the If-Modified-Since header is now supported for static file responses, allowing clients to cache responses and avoid unnecessary re-downloads when the file has not been modified.
  • Auth user variable support - the auth.user variable is now available in variable interpolations for the HTTP server.
  • Trace ID and span ID variable support - the trace.id and trace.spanid variables are now available in variable interpolations for the HTTP server.

HTTP caching

  • RFC 9111 conditional request revalidation - when a client sends Cache-Control: max-age=0 or no-cache, the HTTP cache now performs conditional revalidation using stored ETag and Last-Modified validators instead of bypassing the cache entirely. If the upstream returns 304 Not Modified, the cached response body is served with fresh headers. If the upstream returns 200 OK (content changed), the cached entry is replaced. (http-cache)
  • stale-while-revalidate support - when a cached response’s max-age has expired but a stale-while-revalidate directive is present, the stale response is served immediately to the client while the cache entry remains available within the stale window. This avoids latency spikes for expired cache entries. (http-cache)
  • stale-if-error support - when an upstream revalidation request returns a 5xx error and a stale-if-error directive is present on the cached response, the stale cached response is served instead of the error. This provides resilience against backend failures. (http-cache)
  • must-revalidate and proxy-revalidate enforcement - entries with must-revalidate or proxy-revalidate directives (or s-maxage, which implies proxy-revalidate) are never served stale, even within stale-while-revalidate or stale-if-error windows. (http-cache)
  • Ignore request cache control - when ignore_request_cache_control in cache is enabled, request-based cache control (e.g., Cache-Control: no-cache) is ignored in favor of the configured cache policy.
  • Cache purge propagation - when a cache purge occurs (via PURGE method or X-LiteSpeed-Purge header), the purge can be propagated to other instances via an external control-plane service. Edge instances send webhook POST requests to a configured control-plane URL, which broadcasts PURGE requests to all other registered edges. Loop prevention is handled via the X-Purge-Source: propagation header and origin exclusion. (http-cache)

Automatic TLS

  • HTTP auth for on-demand ask endpoint - when on_demand_ask_auth is configured, HTTP Basic Auth credentials are used for the on-demand ask endpoint, allowing for secure access to the endpoint.
  • Fallback ACME providers — the fallback directive allows configuring multiple ACME providers with sequential failover. When the primary provider fails (e.g., CA outage), Ferron automatically tries the next configured fallback. Each provider maintains its own cached credentials. (tls-acme)

HTTP TLS provider

  • On-demand certificate fetching — the tls-http module now supports on-demand (lazy) certificate retrieval. When on_demand true is set in a wildcard host block (e.g., *:443), certificates are fetched on the first TLS handshake for each SNI hostname, rather than polling a single endpoint at startup.
  • On-demand approval endpoint — the new on_demand_ask, on_demand_ask_auth, and on_demand_ask_no_verification directives control an authorization endpoint that is consulted before fetching a certificate for a hostname. The hostname is sent as a ?domain=<encoded> query parameter.
  • Per-SNI certificate refresh — on-demand fetched certificates are individually refreshed at the configured refresh_interval, ensuring certificate rotation is applied per hostname without affecting other SNIs.

Utilities

  • Formatter utility - a new ferron-fmt command-line tool is available for formatting and validating Ferron configuration files.

Changed

Admin API

  • Reload failure status - the /reload endpoint now returns an error message in the response body when reload fails. Previously, it only indicated that reloading was initiated without any details about why it failed.

Observability & logging

  • Improved error reporting in structured logs - reverse proxy, HTTP, TCP and QUIC error logs now include an error.message attribute when available. Also, some of the reverse proxy error log summaries being overly long have been fixed.
  • OCSP response caching log improvements - OCSP response caching logs are now emitted at info level by default. The ferron.ocsp.cert.primary_san attribute is also included in the log message when available.
  • Trace context always enabled - the force_trace directive has been removed, as trace context creation is now always enabled for every request. Additionally, when no tracing export is configured, Ferron will not generate a new span ID for incoming requests, avoiding unnecessary overhead.

TLS & ACME

  • ACME provisioning cycle log improvements - ACME provisioning logs are now no longer emitted at debug level when a certificate is still valid or loaded from cache, as these messages were redundant with the ferron.acme.domains attribute already included in the main log message.

Proxying

  • Forward proxying CONNECT cycle log improvements - CONNECT connection closure logs are now no longer emitted at debug level when a tunnel is closed to reduce noise in the application logs.
  • Circuit breaker record_5xx toggle - the circuit_breaker directive now supports a record_5xx subdirective (default: false) to control whether upstream 5xx responses count toward tripping the circuit.

Fixed

Observability & logging

  • HTTP/2 and /3 host header log visbility fix - HTTP/2 and HTTP/3 requests now log the host header correctly, ensuring visibility into the host name of the server that handled the request.

TLS

  • OCSP stapling ECDSA issuer fix - when using ECDSA certificates for OCSP stapling, the issuer OID was not being correctly extracted from the certificate, leading to incorrect validation. This has been fixed to ensure the issuer OID is extracted correctly and used for OCSP validation.
  • TLS certificate provider inheritance fix - When using TLS certificates provided by a parent configuration, the certificate provider was incorrectly inherited, leading to errors when attempting to use these certificates in child configurations. This has been fixed to ensure that the certificate provider is correctly inherited and available for use in all child configurations.

HTTP server core

  • Error configuration routing fix - error configuration routing were resolved with a wrong configuration, when using a wildcard hostname in the configuration, even when no error configuration is set. This has been fixed to ensure wildcard hostnames are correctly handled.

Server startup

  • Critical Unix daemon initialization failure fix - when initializing as a Unix daemon, the forked process would exit due to logging re-initialization issues. This has been fixed to ensure that the parent process continues running even in such cases. (GitHub issue)

Ferron 3.0.0-beta.2

Released in June 14, 2026

Breaking changes

If you are upgrading to this beta version, you must update your configuration files to accommodate the following syntax refactors:

  • Rate limit windows - syntax updated to enforce standard duration strings (e.g., 10s, 5m, 1h).
  • OTLP verification - no_verify has been renamed to no_verification and now operates strictly as a configuration flag.
  • OTLP defaults - OTLP sink now uses log_style modern default, which may break existing configurations with custom OTLP log formats.
  • Proxy configuration - syntax for passive/active health checks, load balancing algorithms, and connection retries has been unified into a cleaner, more consistent format.
  • Incoming trace context discarded by default - Ferron no longer trusts incoming traceparent, tracestate, or baggage headers unless trust_request true is explicitly set in the trace block. Previously, incoming trace context was always parsed and used as the parent span. To restore the old behavior, add trust_request true to your http { trace { ... } } block.

Added

Modules

  • http-abuseban - a new module for lightweight, native Fail2ban-like IP banning with temporary lockouts based on rate limit breaches and brute-force failures.
  • http-traceid - a new module that injects the current request’s trace ID into HTTP response headers (configurable header name, optional on-demand reflection via X-Ferron-Trace-Reflect).
  • tls-http - support for obtaining TLS certificates from a remote HTTP endpoint, featuring automatic refresh cycles and dedicated observability metrics.
  • OS metrics - added Windows support for native process metrics in the metrics-process module.

Reverse proxy & load balancing

  • circuit_breaker - new directive with rolling failure windows, temporary backend ejection, and half-open recovery states.
  • p2c_ewma - a new adaptive, latency-aware load balancing algorithm combining Power of Two Choices with Exponentially Weighted Moving Average (EWMA) latency scoring.
  • Session affinity (sticky sessions) - native support for cookie, header, ip, and hash types utilizing a Ketama-style hash ring for deterministic backend routing.
  • String interpolation - upstream URLs and Unix socket paths now support interpolated strings.
  • SRV routing - added active health check support for SRV upstream URLs.
  • Outbound mTLS - reverse proxy upstreams now support presenting client certificates (cert and key subdirectives) for mutual TLS authentication with HTTPS backends, including per-upstream credential scoping and SRV upstream support.

DNS & ACME

  • 58 new DNS providers - native ACME DNS-01 challenge support expanded to include Alibaba Cloud DNS, Azure DNS, ClouDNS, Hetzner DNS, Oracle Cloud DNS, Vercel, Vultr, Yandex Cloud DNS, and 50+ others.
  • CLI arguments in post-obtain command - added support for passing CLI arguments to the post-obtain command in automatic TLS configurations.

HTTP server core

  • basic_auth_concurrency - global directive to limit concurrent, resource-heavy password verification tasks across all basic_auth blocks.
  • Cache purging - native PURGE HTTP method support for targeted cache invalidation via the purge_method and purge_allowed_ips subdirectives.
  • force_trace - global directive to force trace context creation for every request even when tracing is not explicitly enabled by a module, useful for debugging or log correlation.
  • trust_request - new directive in the trace block to accept incoming traceparent, tracestate, and baggage headers as the parent trace context. Default: false. When disabled (default), incoming trace headers are discarded and Ferron generates a fresh trace ID for each request.
  • Optimal server-preferred compression algorithm - now supports automatic detection of the optimal compression algorithm based on client preferences (GitHub issue).

Authentication

  • Forwarded authentication backend chaining - the forwarded authentication module now supports chaining multiple backends together, allowing for flexible and secure multi-backend authentication scenarios.

Observability & metrics

  • Edge-case visibility - granular HTTP observability metrics for pre-handler failures, server redirects, client-IP rewrites, CORS preflights, connection lifecycle failures, forward-proxy outcomes, reverse-proxy failures, and static-file response outcomes.
  • Admin sinks - added a dropped-events admin metric for non-blocking observability sinks.
  • Unix file descriptor metrics - added process.unix.file_descriptor.count (UpDownCounter) to track the change in number of unix file descriptors since the last measurement. (Linux only)
  • Backend exclusion tracking - new ferron.proxy.backend.excluded counter that records each exclusion event with backend_url and reason attributes, covering passive-check failures, open circuit breakers, retry exclusion, and overloaded backends.
  • Retry observability - new ferron.proxy.retry.count counter and ferron.proxy.retry.final gauge to track retry attempts and whether the final attempt succeeded.
  • Connection pool metrics - new ferron.proxy.pool.hit and ferron.proxy.pool.miss counters, plus ferron.proxy.pool.idle and ferron.proxy.pool.outstanding worker-scoped gauges for per-upstream connection pool depth monitoring.
  • Connect latency & TTFB - new ferron.proxy.connect.latency and ferron.proxy.ttfb histograms measuring TCP connection establishment time and time to first response byte.
  • Health check instrumentation - new ferron.proxy.health.success, ferron.proxy.health.failure, and ferron.proxy.health.duration metrics for active health probe outcomes.
  • Circuit breaker metrics - new ferron.proxy.circuit.state gauge (0/1/2 for Closed/HalfOpen/Open) and ferron.proxy.circuit.open_total counter tracking circuit breaker state transitions.
  • Unified certificate expiration gauge - a single ferron.tls.certificate_not_after gauge is emitted by every TLS provider (manual, acme, http, local) whenever a certificate is mounted into the in-memory rustls context. The value is the certificate notAfter field as Unix epoch seconds; attributes are ferron.host (SNI hostname or IP), ferron.tls.provider (provider name), and crypto.certificate.serial_number (lowercase hex). Replaces the previous provider-specific expiration gauges.
  • Baggage promotion - new baggage sub-directive in observability backend blocks (otlp, prometheus) to promote specific W3C Baggage keys into telemetry attributes for logs, metrics, and traces. Supports per-signal filtering and a max_distinct cap to prevent high-cardinality metric label explosion.
  • Baggage propagation - Baggage is now propagated from incoming requests to outgoing ones, allowing distributed tracing context to be preserved across service boundaries.
  • CGI/FastCGI/SCGI observability metrics - added request count, failure count, upstream duration, and stderr error metrics for the http-fcgi, http-cgi, and http-scgi modules. FastCGI also exposes connection pool wait and upstream duration metrics; CGI exposes process duration and exit code metrics; SCGI exposes upstream duration metrics.
  • log_style directive - new log_style legacy|modern directive in the OTLP observability backend block to opt into OTEL-style structured logs. In modern mode, each log record publishes a short summary body plus typed per-event attributes (string, bool, integer, float) instead of the human-readable message. Access logs are remapped to OTEL semantic conventions (url.path, http.request.method, http.response.status_code, client.address, http.server.request.duration, etc.). The default legacy mode preserves existing behavior.
  • Application log formatters via error_format - the error_log directive and provider file now support an error_format subdirective to choose how application (error) log messages are formatted. The text formatter (default) produces human-readable lines with timestamp, level, optional trace ID, and message. The json formatter produces structured JSON records with summary, level, target, attributes, and trace_context fields. Formatter implementations are provided by the logformat-text and logformat-json modules respectively.
  • Logs for OTLP errors - Errors with the logs, metrics and traces providers are now logged.

Admin API

  • Added GET /reload and GET /runtime endpoints to the admin listener.

Configuration syntax

  • Globs in include directives - include "path.conf" now supports glob patterns, e.g. include "/etc/ferron/conf.d/**/*.conf".

Configuration validation

  • Best-practice enforcement - ferron doctor subcommand for validating best practices across the configuration.
  • Print “all good” message - a message is now printed when the configuration is valid with no diagnostics.
  • JSON configuration validation results are now printed to stdout when --json is specified, improving observability for automated tools and CI/CD pipelines.
  • Unused subdirectives are now reported as well as directives.

Docker

  • Entrypoint configuration - Docker images now include a dedicated entrypoint config with JSON console logging, trace ID forcing, and an include /etc/ferron/conf.d/**/*.conf directive for user-provided split configurations.

Changed

Reverse proxy

  • Weighted load balancing - both least_conn and round_robin algorithms now support per-upstream weight directives for proportional traffic distribution.
  • Metric attributes - multiple HTTP reverse proxy metrics now embed the specific backend URL or Unix socket path as a context attribute.

DNS & TLS

  • Early OCSP verification - OCSP responses are now strictly verified before being cached and stapled.
  • Verbose errors - significantly improved error reporting layouts for local automatic TLS and specific TLS handshake failures.
  • /var/cache/ferron-acme prioritized over user’s cache dir - for Docker images with Ferron running as root, the ACME cache directory is now /var/cache/ferron-acme, which takes precedence over user’s ACME cache directory.

HTTP server core

  • Safe configuration reloads - configuration failures during a live reload no longer crash or stop the server; errors are logged, and execution safely continues using the previous valid configuration.
  • Smarter brute-force locking - protection now locks by IP address instead of username, preventing malicious actors from intentionally locking out legitimate users.
  • Accurate status codes - refactored error handling to return context-aware status codes over generic 500 or 404 errors:
    • File pipeline returns 408 Request Timeout if a request takes too long (instead of 404).
    • Basic Auth returns 429 Too Many Requests when max failed attempts are reached.
    • File serving errors return 403 Forbidden (permissions) or 400 Bad Request (invalid filenames) instead of 500.
  • Support for multi-range partial static file serving - now supports serving multiple ranges from a single request, improving efficiency and reducing latency.
  • String interpolation - forwarded authentication now supports interpolated string values for backend URLs.
  • Security tightening - URL canonicalization now strictly rejects paths containing null bytes (\0 or %00).
  • Cache cleanliness - X-LiteSpeed-Cache headers are no longer emitted by default; can be re-enabled via the emit_litespeed_headers subdirective.

Observability & tracing

  • Unified request tracing - HTTP tracing now rolls up into a single ferron.request root span with nested child spans for pipelines, stages, file-serving, and error pipelines.
  • Log correlation - OTLP request and access logs now include the active request span context out of the box.
  • Backend exporting - admin API metrics are now also pushed directly to configured observability backends, not just the local admin endpoint.
  • Cardinality control - Prometheus label values are now sanitized to heavily reduce high-cardinality label inflation.
  • Structured log events - every log emission site now carries a short OTEL-friendly summary plus typed attributes, enabling downstream OTLP consumers to receive structured events without changing existing console or file log output.
  • Trace ID in console and file logs - console and file loggers now prefix log messages with [trace=<span_id>] when a trace context is available, enabling grep-based filtering by trace ID.
  • Trace context injection for CGI/FastCGI/PHP-FPM/SCGI - trace context headers are now automatically propagated to the backend environment via CGI/FastCGI/PHP-FPM/SCGI.
  • Incoming trace context discarded by default - Ferron now generates a fresh trace ID for every request by default, discarding incoming traceparent/tracestate/baggage headers unless trust_request true is configured. Existing deployments relying on incoming trace context must add trust_request true to the trace block.
  • Sanitized trace header injection - inject_trace_headers now removes any existing traceparent, tracestate, and baggage headers before injecting new values, preventing header duplication or conflicts.

Core runtime

  • Unified durations - improved configuration-wide consistency for duration formatting values.
  • Graceful shutdown - the server process now handles standard Unix SIGTERM signals for seamless graceful shutdowns.
  • Frictionless local TLS - the server now issues a clean warning if local automatic TLS is configured but the cache directory isn’t writable, instead of refusing to boot.

Docker

  • Split configuration layout - Docker images now use a two-file layout: /etc/ferron/ferron.conf as the entrypoint config and /etc/ferron/conf.d/ for user configuration files. Docker Compose bind mounts now target the conf.d/ directory.

Package configuration

  • Log rotation and structured logging - Default package configurations now include log rotation settings and commented-out structured logging and trace ID format options.

Fixed

Reverse proxy

  • Fixed a bug where pool limits weren’t being respected when pulling connections from the pool, leading to possible handle exhaustion.
  • Fixed a bug where reverse proxy boolean subdirectives with empty values (implying true) were being ignored.
  • Fixed an issue where the proxy failed to strip headers specified by the Connection header per RFC 7230.

DNS & ACME

  • Fixed an “invalid socket address” error that broke RFC 2136 dynamic DNS updates for the ACME DNS-01 challenge.
  • Resolved an edge case where OCSP stapling failed to immediately fetch the response after new certificates were registered.

HTTP server core

  • Auth bypass closed - fixed a critical flaw where a misconfigured forwarded authentication block could result in bypassing authentication entirely.
  • Cache thundering herd fixed - implemented request coalescing for cache misses to prevent thundering herd scenarios when multiple requests hit the same uncached resource simultaneously.
  • Several HTTP/1.x protocol handling bugs fixed - fixed chunked-length encoding DoS vulnerability and other protocol-related issues (see vibeio-http changelog).
  • Fixed a 500 Internal Server Error when using the auth_to { ... } syntax inside forwarded authentication blocks.
  • Fixed a bug where case-insensitive HTTP cache control directives were not recognized correctly.
  • Fixed a bug where CONNECT requests with authority-form URIs were erroneously blocked by the URL canonicalizer.
  • Fixed a bug where HTTP connections couldn’t be accepted on 32-bit Windows (see vibeio changelog; GitHub issue).
  • Fixed a bug where HTTP connections were accepted by only one OS thread on Windows.
  • Fixed a bug where HTTP timeout durations were not being respected correctly.
  • Fixed a bug where TLS certificate resolver from domain name level higher (non-wildcard) was incorrectly used for TLS handshakes if the one from the domain name level matching the requested SNI isn’t present.
  • Fixed HTTP-to-HTTPS redirects to correctly target the original requested URL rather than internal rewritten URLs.

Observability

  • Log injection fixed - implemented strict sanitization of log fields to prevent potential log injection attacks for plain-text logs via malicious header values or other user input.
  • Fixed a data blind spot where malformed and timed-out requests rejected before normal handler completion went uncounted by the observability pipeline.
  • The server now removes sensitive HTTP headers from access logs (such as cookie, authorization), to prevent sensitive data exposure in log output.
  • Corrected inaccurate memory metrics that were calculating values relative to initial memory usage instead of absolute usage.

Security

  • DNS rebinding fixed - closed a vulnerability where forward-proxy DNS validation could be bypassed via a race condition combined with a DNS rebinding attack.
  • Fixed an issue where forward-proxy allowed ports and denied IP addresses were being treated as additive rather than strictly respecting user configuration overrides.

Runtime operations

  • Fixed an Admin API-initiated reload loop that caused infinite configuration reload loops.
  • Fixed admin API over-redacting configuration directives in /config endpoint responses, which could lead to confusion when verifying configuration values.
  • Eliminated a rate limiting race condition when initializing a brand new key bucket, which previously allowed traffic to briefly exceed configured capacities.
  • Fixed manual TLS session ticket key rotation to properly read from configured key files instead of silently falling back to in-memory generation.
  • Fixed a bug on Linux where io_uring could not be explicitly disabled through the server configuration file.
  • Fixed an edge case where cached responses replaced by non-cached default error pages could be returned stale.

Ferron 3.0.0-beta.1

Released in May 5, 2026

Added

  • CLI utility for hashing passwords.
  • CLI utility for pre-compressing static files.
  • CLI utility for translating Ferron 2 configurations into Ferron 3 ones.
  • CLI utility for zero-configuration serving.

Changed

  • Non-existent webroots now lead to 404 Not Found errors instead of 500 Internal Server Error errors.

Fixed

  • Partial hostname resolution match in HTTP server could lead to incorrect routing.
  • Redirects configured with status directive didn’t have some placeholder locations (such as $1) replaced when using a regex match.
  • Redirects configured with status directive didn’t lead to any destination.
  • Reverse proxy was sometimes routed to wrong backend server.
  • Some default cache paths were unwritable in some cases.
  • Unknown directives in global blocks for status directive (even though they’re known in host blocks) caused the web server to fail to start.
  • When using OTLP, access logs were emitted with “access_log” body, not actual access logs.

Ferron 3.0.0-alpha.3

Released in April 23, 2026

Added

  • Multiple DNS providers for DNS-01 ACME challenge.
  • Support for CGI (Common Gateway Interface).
  • Support for FastCGI (including PHP-FPM).
  • Support for forwarded authentication.
  • Support for SCGI (Simple Common Gateway Interface).

Fixed

  • Connections to HTTP/2 backends in reverse proxy were aborted.
  • Error interception configuration blocks weren’t applied properly.
  • HTTP to HTTPS redirect wasn’t enabled by default.
  • HTTP upgrades weren’t performed properly.
  • HTTP-01 ACME challenge failed due to challenge not being served for implicit automatic TLS.
  • On-demand automatic TLS wasn’t functioning correctly.
  • Some ACME events were logged only to the console, not observability backends.
  • TLS certificates with local CA weren’t checked if they’re expired.

Ferron 3.0.0-alpha.2

Released in April 17, 2026

Added

  • Active health checking in reverse proxy support.
  • Automatic TLS with local certificate authority (CA).
  • Experimental HTTP/3 support.
  • map directive for mapping variables.
  • Prometheus metrics export support.
  • Response body string replacement support.
  • Support for body interpolation in status directives.
  • Support for interpolated strings in header values.
  • W3C Trace Context (traceparent and tracestate) propagation and generation.

Changed

  • Improved the request URL normalization.
  • Requests with multiple Host headers are now rejected.

Fixed

  • PROXY protocol setting, connection retry setting and error interception weren’t working for reverse proxy.
  • Zerocopy static file serving wasn’t working properly on Linux, because it wasn’t enabled.

Ferron 3.0.0-alpha.1

Released in April 10, 2026

Changed

  • First alpha release of Ferron 3.