github ferronweb/ferron 3.0.0-beta.5

4 hours ago

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.

Don't miss a new ferron release

NewReleases is sending notifications on new releases.