github ferronweb/ferron 3.0.0-rc.9

pre-release2 hours ago

Breaking changes

Static file serving

  • ferron.static.bytes_sent and ferron.static.files_served metrics: these metrics are no longer emitted by the static file serving stage. This is to reduce noise in the metrics output. Check your observability workflows.

Added

Observability

  • Response header fields in access logs: HTTP access logs now include response header fields, allowing visibility into HTTP response headers, for example for HTTP redirects.

HTTP server core

  • Support for interpolated string sources in map: added support for interpolated string sources (in addition to variable name sources) in map directive, allowing mapping multiple values at once into one value.

Changed

HTTP server core

  • http block inheritance: a host block now inherits each global http directive that it does not set itself. Configurations that relied on a host block replacing the global http block see the global values apply again for directives the host omits.

Gateway interfaces

  • REMOTE_ADDR and SERVER_ADDR environment variables: REMOTE_ADDR and SERVER_ADDR environment variables are now set to the canonical IPv4/IPv6 representation of the client and server IP addresses, respectively, so that they can be used for access control and logging consistently.

Reverse proxying

  • Forwarded header client IP canonicalization: the client IP in X-Forwarded-For and Forwarded headers is now canonicalized to the standard IPv4/IPv6 representation, so that it can be used for access control and logging consistently.

Automatic TLS

  • Reduced span attribute noise: request spans related to HTTP-01 challenge no longer include attributes that are not useful for observability, reducing noise in observability pipelines.

Fixed

HTTP server core

  • Early hints header fix: previously, when early_hints block was configured with multiple Link header values, only the last one would be sent in the response. This has been fixed to send all header values.
  • Variable resolution fix: previously, if Ferron cannot resolve a variable, it kept the placeholder as name (instead of documented {{name}}).
  • Per-host trace and trace_sampling fix: previously, http { trace { generate, trust_request } } and http { trace_sampling ... } were silently ignored in host blocks and were only read from the global block, so per-host trace-header trust and per-host sampling did not work. Ferron now resolves both from the matched host block, falling back to the global block.
  • protocol_proxy fix: previously, http { protocol_proxy true } in the global configuration block had no effect unless the configuration also had a host block without a hostname, and the ferron doctor check for it never ran. Ferron now reads the setting from the global http block or from the host block without a hostname, and reports the best-practice violation.
  • protocols validation fix: ferron validate did not check http { protocols } values because it looked for the directive in the enclosing block instead of the http block. Invalid values such as h4 are now reported as a configuration error.
  • options_allowed_methods fix: the directive never reached the response.
  • options_allowed_methods response header fix: previously, a resolved value that could not be sent as an Allow header, such as an interpolated value carrying a line break, panicked the worker task and dropped the connection for every OPTIONS * request. Ferron now falls back to the default method list and reports an unusable configured value with ferron validate.
  • control_plane.span_links.sampled boolean flag fix: previously, the bare directive form read as false instead of true, and ferron validate rejected it. Ferron now reads the flag like every other boolean directive.
  • basic_auth realm response header fix: previously, a realm value containing a control byte or a line break panicked the worker task on every 401 or 407 response, because the value is embedded in the authentication challenge header. Ferron now answers without a challenge header and reports an unusable realm with ferron validate.
  • Conditional response header fix: previously, a file_cache_control value that could not be sent as a Cache-Control header panicked the worker task on the 304 and 412 precondition paths. Ferron now omits the header and reports an unusable value with ferron validate. It lives inside the http block, but Ferron read it as a top-level directive of the resolved configuration, so every OPTIONS * response used the built-in default Allow list. ferron validate and ferron doctor accepted the directive, which made the failure silent.
  • http block inheritance fix: a host block that set any http directive replaced the whole global http block, so every global setting the host did not repeat was dropped. For example, a host that only set timeout lost the global protocols, h1_enable_early_hints, and HTTP/2 and HTTP/3 settings. Ferron now layers the two blocks per directive, matching how directives in every other block inherit.
  • HTTP/2 setting range validation: h2_max_frame_size and h2_initial_window_size accepted values outside the ranges that HTTP/2 requires, so Ferron could advertise a SETTINGS frame that conforming clients reject. ferron validate now reports these values.
  • h3_qpack_blocked_streams usage fix: the directive takes a stream count, but ferron directives reported it as a boolean flag.

Observability

  • Baggage with trust_request false fix: previously, when trust_request false was set, the server would still propagate baggage from incoming requests to outgoing requests. Now, baggage is not propagated when trust_request false is set.
  • Host observability block fix: previously, when specifying multiple observability blocks inside a host block with IP address or hostname, only the last one was effective. This has been fixed to use all observability blocks, just like with wildcard host blocks.
  • Prometheus scrape metrics fix: the ferron_prometheus_scrape_total and ferron_prometheus_scrape_errors_total metrics have been renamed to ferron_prometheus_scrape and ferron_prometheus_scrape_errors, respectively (previous behavior would involve _total_total suffix).
  • Circuit breaker metric types fix: ferron.proxy.circuit.open_total and ferron.proxy.circuit.half_open_timeouts were reported with a signed value on a counter metric. No observability backend supports that combination, so both metrics were silently dropped and never reached Prometheus or OTLP. open_total now reports as a gauge that rises when a circuit opens and falls when it closes or moves to HalfOpen, and half_open_timeouts reports as an unsigned counter.
  • Active health check state per resolved address fix: previously, health check state was keyed by the configured upstream URL, so every address behind one hostname shared a single state entry. One unreachable address marked all of them unhealthy, which returned 503 for every request while healthy capacity was still available. Ferron now tracks health state per resolved address, the same way it already tracks circuit breaker state, and probes each address on its own connection.
  • ferron.proxy.dns_status attribute consistency fix: previously, backend scoped proxy metrics were emitted with different attribute sets depending on the code path. ferron.proxy.circuit.state and ferron.proxy.circuit.flapping were exported as two separate Prometheus series for the same backend, one with ferron.proxy.dns_status and one without, so any aggregation over the metric counted a backend twice and the two series could disagree. Ferron now always reports ferron.proxy.dns_status on backend scoped proxy metrics and only gates ferron.proxy.backend_resolved_ip behind metrics_resolved_ip, matching what the directive documents.
  • Active health check probe metrics fix: previously, the probe duration, success, and failure metrics were emitted through a process wide sink built from the global configuration, so they were discarded unless an observability provider was configured globally. The probe metrics now go to the sink of the host that owns the upstream, so a provider configured in a host block receives them. The process wide connection pool and DNS result cache metrics are unchanged, and the documentation now states that they need a globally configured provider.
  • Prometheus exporter histogram buckets fix: previously, metric-specific histogram buckets were ignored, leading to default histogram buckets being used instead and possible incorrect bucket values being reported.

Access control

  • bcrypt password hash fix: Ferron 3.0.0-rc.8 added support for bcrypt password hashes, however it could not be used due to configuration validation errors. The validator has been now updated too.

HTTP caching

  • Cache configuration fix: previously, cache { } didn't enable caching (but cache and cache { ... } did), which could cause debugging friction.
  • ferron.cache.evictions{reason="expired"} metric fix: previously, the ferron.cache.evictions{reason="expired"} metric was not emitted when cache entries expired, which could cause debugging friction.
  • Cache purge scope fix: previously, cache purge requests with X-LiteSpeed-Purge: * header would purge all cache entries, even those that were not created by the current host when configured in a block with ambiguious hostname.
  • Cache hostname fix for wildcard host blocks: previously, cache entries created by a wildcard host block (like *.example) would be incorrectly stored, leaking cache entries across hosts.
  • Cache purge host fix: previously, PURGE requests could miss matching entries because the request host was read after the proxy stage consumed the request. The host is now taken from the request headers saved before proxying.

Static file serving

  • mime_type response header fix: previously, a mime_type value that could not be sent as a Content-Type header, such as one containing a line break or a control byte, caused every static file response for the matching extension to panic and the connection to drop without a response. ferron validate accepted such a value. Ferron now reports the value as a configuration error, ignores the mapping when it is still reached at request time, and never panics while building a static file response.
  • multipart/byteranges part header fix: previously, the mime_type value was written into the content-type line of each multipart/byteranges part as given, so a value containing a line break could inject extra lines into the response body.
  • If-None-Match wildcard fix: the server now responds with a 304 response when a If-None-Match: * request header is sent, instead of a 200 response.
  • HTTP ranges with precompression support: previously, when precompressed file was present and precompression was enabled, a range request would lead to a response with parts of compressed file, which could be malformed.
  • If-Modified-Since and If-Unmodified-Since malformed date fix: the server now ignores malformed date values in If-Modified-Since and If-Unmodified-Since request headers instead of responding with a 400 (Bad Request) error.
  • HTTP range bytes=-0 fix: the server now correctly handles bytes=-0 range requests, treating them as unsatisfiable (416) instead of invalid syntax (ignored).
  • ETag + Date conditional request precedence fix: previously, If-Modified-Since was evaluated even if If-None-Match was present, which could lead to incorrect behavior. Similar happened with If-Unmodified-Since and If-Match.
  • Windows directory listing fix: previously, the server would respond with a 403 Forbidden response when trying to access a directory from HTTP client when the server is running on Windows, even when the directory listings are enabled. (GitHub issue)

HTTP compression

  • 304 Not Modified fix: HTTP dynamic content compression is no longer applicable for 304 (Not Modified) HTTP responses.
  • Accept-Encoding wildcard fix: the server now correctly handles Accept-Encoding: * request headers, selecting the most appropriate compression algorithm from the server's supported ones.

Reverse proxying

  • Connection: upgrade header fix: previously, when there was a request header with Upgrade inside the Connection header value (if the header wasn't exactly Upgrade), it would erroneously set to upgrade.
  • Request body chunked encoding fix: previously, when the request body was chunked-encoded, the server would wrongfully strip Transfer-Encoding header from the request before sending it to the upstream server, which could cause issues with some upstream servers that expect the header to be present.
  • Retry budget fix: previously, cross-backend failovers could consume a retry token before checking whether another backend was available, and max_retry_rate was accepted but not enforced. The budget now charges only retries that can be attempted and enforces the configured retry-rate share.
  • Circuit breaker half-open timeout fix: previously, the circuit breaker would not transition from half-open to closed when the HTTP pipeline timeout occurred, which could cause the circuit breaker to remain half-open until the process restart.

URL rewriting

  • Bare boolean subdirective fix: fixed bare form of once (and similar) boolean flags not being effective.

Content replacement

  • replace_last_modified flag: fixed bare form of replace_last_modified flag not being effective.

Don't miss a new ferron release

NewReleases is sending notifications on new releases.