Added
Security
- Configurable symlink traversal protection — the new
disable_symlinksdirective allows configuration of symlink handling during static file serving (false,true, orif_not_owneron Unix). When enabled, symlinks are detected without following them during path traversal, preventing symlink-based escape attacks from outside the configured webroot. Defaults tofalsefor backward compatibility. - Error rate threshold — the
abuse_protectiondirective now supports anerror_rate_thresholdsub-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.durationhistogram), handshake count (ferron.tls.handshake.totalcounter), and active connections (ferron.tls.connections.activeup-down counter). Each metric carriesferron.host,tls.protocol.version, andtls.cipher_suiteattributes, enabling per-host visibility into TLS performance and client compatibility. - TLS attributes on request traces — the
ferron.requestroot span now includestls.protocol.versionandtls.cipher_suiteattributes 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 aferron.hostdimension, and a newferron.ocsp.stapling.hit_totalcounter 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.addressandserver.addressattributes, enabling IP-based correlation and troubleshooting. - Load-balancer selection score metric — new
ferron.proxy.lb.scoreGauge metric exposes the combined selection score used by thetwo_randomandp2c_ewmaload-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_truncatedCounter metric and structured warning log detect when an upstream backend closes the response stream before the declaredContent-Lengthbytes 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_breakerdirective now supports an optionallatency_thresholdsub-directive. When configured, upstream responses exceeding this duration count as failures toward tripping the circuit, alongside transport failures and (optionally)5xxresponses. This allows circuit breakers to eject pathologically slow backends that return successful status codes. - Circuit breaker flapping detection — the
circuit_breakerdirective now supportsflapping_transitionsandflapping_windowsub-directives for detecting rapidly oscillating upstream backends. When an upstream transitions between circuit breaker states more thanflapping_transitionstimes (default: 3) withinflapping_window(default:10s), a single warning log is emitted and theferron.proxy.circuit.flappingGauge 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_breakerdirective now supports aslow_startduration 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_ipdirective (default:false) controls whetherferron.proxy.backend_resolved_ipis 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 algorithms —
random,two_random, andp2c_ewmanow support per-upstreamweightdirectives for proportional traffic distribution, joininground_robinandleast_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 configweightsubdirective 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, andlogical_dns trueupstreams are unaffected. Usedns_serversto 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_timeoutsubdirective 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_proxyspan 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_tofield ofUpstreamInneris exposed asferron.proxy.backend_resolved_ipmetric attribute for all backend-related metrics, allowing observability of the actual IP addresses backends are connected to. This complements existingbackend_urlandbackend_unix_pathattributes. - 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, andferron.proxy.retry_count. Cache results includeferron.cache.resultandferron.cache.zone. Rate limit decisions includeferron.ratelimit.resultandferron.ratelimit.zone. Auth outcomes includeferron.basicauth.resultandferron.fauth.result. Abuse protection includesferron.abuseban.action. Static files includeferron.static.file_path. CGI/FastCGI/SCGI backends include their respectivebackend_urlandscript_pathfields. 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, orhalf_open), and theferron.proxy.circuit.stategauge 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 separatestat-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.HttpFileContextnow carriesfile: ReusedFileinstead ofmetadata: 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_breakerdirective is now enabled by default for allproxyconfigurations. Circuit breakers track transport failures (TCP connect errors, TLS errors) and optionally upstream5xxresponses (whenrecord_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, setcircuit_breaker false. ferron.proxy.requestsmetric with backend URL — theferron.proxy.requestscounter 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_canonicalandserver_ip_canonicalare now available as OpenTelemetry-style log attributes and trace span attributes, replacing the legacyclient_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-acmeandtls-httpnow useferron-tls-acmeandferron-tls-httplog targets respectively instead offerron_tls_acmeandferron_tls_http. - Circuit breaker gauge value fix - the
ferron.proxy.circuit.stategauge now correctly uses1foropenand2forhalf_open, matching the constant definitions. Previously these values were swapped.