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 truein theobservability.prometheusblock. 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_idandspan_idof 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 whenendpoint_native_histogramsisfalse(the default), since native histograms and exemplars are mutually exclusive. - Configuration content hash — new
ferron.admin.config_mtimeGauge metric andconfig_file_hash/config_file_mtimefields on the/statusAdmin 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_secondsGauge metric exposes aggregated remaining TTL for DNS cache entries (min,max,avgaggregation viaaggregationattribute). A companionferron.proxy.dns.cache_entriesGauge 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_limitandferron.proxy.pool.global_limitpool metrics are now available for monitoring connection pool usage. - Method and idempotency labels on retry metrics — the
ferron.proxy.retry.countCounter andferron.proxy.retry.finalGauge now includehttp.request.method(categorized:GET,POST,PUT,DELETE,PATCH,HEAD,OPTIONS,CONNECT,TRACE,_other) andferron.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_requestCounter metric is now emitted for every proxied request, withbackend_urlandbackend_unix_pathattributes. This allows operators to see traffic amplification effects when multiple backends are selected for a single request.
Security
- Rate limit throttling — the
rate_limitdirective now supports athrottlesubdirective 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_budgetdirective implements a token-bucket-based retry budget that limits retries to a configurable fraction of steady-state traffic. When enabled alongsideretry_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 returns503 Service Unavailable, preventing cascading retry storms from amplifying backend failures into a self-inflicted DDoS. Supportsmax_retry_rate(default: 10%),max_tokens(default: 10), andrefill_rate(default: 2.0 tokens/sec) subdirectives. New metrics:ferron.proxy.retry.budget_exhausted(Counter) andferron.proxy.retry.budget_tokens_available(Gauge).
HTTP server core
- JSON error responses — the new
json_errorsdirective in thehttp-jsonerrormodule enables structured JSON error responses for HTTP 4xx/5xx errors. Supports both RFC 9457application/problem+json(withtype,title,status,detail, and optionaltrace_idfields) and simpleapplication/jsonoutput formats. ThetypeURI is configurable (default:about:blank), with support for{status}placeholder substitution (e.g.,https://http.dev/{status}). Uselocation /api { json_errors true }to scope JSON errors to REST API endpoints. (http-jsonerror) - Client certificate common name variable — the
mtls.cnvariable 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_resolvetrace span withferron.file_resolve.request_path,ferron.file_resolve.root_path,ferron.file_resolve.outcome, andferron.file_resolve.resolved_path(success) orferron.file_resolve.last_candidate_path(error) attributes. Resolution error paths (403 Forbidden, 400 Bad Request) now includeferron.file_resolve.*fields in access logs, replacing the previous "black box" behavior where the file resolution process produced no observability signals. Directory listings now emitferron.static.dir_pathtrace span and access log attributes, and contribute to theferron.static.responsescounter metric withferron.static.outcomevalues"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.0instead oftext/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.namein traces renamed toferron.stage.name— renamedstage.nametoferron.stage.nametraces 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_ipdirective now also emits aferron.proxy.dns_statusattribute 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.excludedmetric name has been renamed toferron.proxy.backends.excludedfor consistency with other reverse proxy metrics.