github ferronweb/ferron 3.0.0-beta.9

pre-release3 hours ago

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.

Don't miss a new ferron release

NewReleases is sending notifications on new releases.