Today, we’re releasing Ferron 3.0.0-beta.11, the eleventh beta version of what it would be Ferron 3.
This release involves significant performance improvements (especially in static file serving), but also multi-IP binding, improved cache observability, various bugfixes and more.
Benchmark spotlight: Ferron 3 performance gains
In 3.0.0-beta.11, we shifted the default I/O backend in zincio back to epoll for standard Linux workloads after extensive profiling revealed that epoll delivers lower event-loop overhead and tighter latency characteristics for page-cached static file delivery and standard reverse proxying.
To measure these improvements, we ran synthetic HTTPS benchmark suites using wrk across popular web servers under heavy concurrency.
Benchmark environment
- Hardware: AMD Ryzen 7 8700G
- OS / kernel: Linux 7.1 (Omarchy, based on Arch Linux)
- Workload: Static ~2.6 KB
index.htmlfile over HTTPS (HTTP/1.1) - Command:
wrk -c 2500 -d 10 -t 16 https://localhost/
Results
| Web server | Requests/sec | Avg latency | Max latency | Transfer/sec | Socket errors |
|---|---|---|---|---|---|
| NGINX | 348,801.40 | 5.94 ms | 116.49 ms | 0.93 GB/s | 0 |
| Ferron 3 (dev / beta.11, epoll) | 333,299.71 | 7.04 ms | 128.78 ms | 0.92 GB/s | 0 |
| Ferron 2 (epoll) | 302,917.52 | 9.04 ms | 233.13 ms | 818.99 MB/s | 0 |
| Ferron 3 (beta.8, epoll) | 293,941.50 | 8.67 ms | 193.63 ms | 831.72 MB/s | 0 |
| Caddy | 124,584.30 | 97.21 ms | 2,000.00 ms | 343.61 MB/s | 22 timeouts |
Key takeaways
- +13.4% speedup over 3.0.0-beta.8: internal runtime refinements in
zinciopushed throughput from 293.9k req/sec up to 333.3k req/sec. - Reclaiming the lead over Ferron 2: 3.0.0-beta.11 officially leapfrogs Ferron 2’s baseline (302.9k req/sec) with lower average latency (7.04 ms vs 9.04 ms) and tighter tail latency.
- Closing the NGINX gap: Ferron 3 sits within 4.4% of NGINX’s C-based transport core while delivering memory safety, built-in LSCache support, and automated TLS out of the box.
Your requested features
Multi-interface binding
After we saw someone replying on Threads asking for new feature, we implemented a feature where the web server can listen on multiple IP addresses (from different network interfaces). This allows separating management traffic from public-facing traffic on a single machine.
You can use this feature like this:
{
tcp {
listen_ip 10.0.0.1 192.168.1.1
}
}Forwarded auth configuration
After seeing a GitHub issue related to OAuth2 Proxy integration, we found out that Ferron 3 didn’t support that, because forwarded authentication response interception wasn’t supported.
So we added several forwarded authentication configuration subdirectives to support applications like OAuth2 Proxy:
intercept_errors, which intercepts error responses for error handlers, similarly to what reverse proxy is doingrequest_headerfor modifying request headers, also similar to reverse proxy
Also, X-Real-IP request header is now included by default for forward auth requests, just like the reverse proxy.
Improved cache observability
We truncated cache key (up to 48 characters) with the query string removed, useful for diagnosing why a specific request missed. Optionally with a short non-reversible tag (q=<16 hex chars>), and if the base itself had to be truncated, a second tag (h=<16 hex chars>).
Also, cache decision reasons are now attached to access log entries and cache metrics.
UX improvements
- Empty configuration validation: added a check for empty configurations (no ports, no global directives/matchers) to return an error instead of silently starting.
- Reduced startup logs: removed multiple startup logs for various features to reduce noise in the logs.
- Certificate and private key mismatch fix: previously, when a certificate and private key did not match for a given host, this was silently allowed. This has been changed to error out on the startup of the server.
Bug fixes
- Protocol + IP + port host block split fix: previously, the protocol+IP+port combination was not properly split when used as a host block (for example,
http 127.0.0.1:18081lead to two host blocks:http 127and0.0.1:18081). request.uriinterpolation fix: previously,request.uriinterpolation included the full request URL, including thehttp/httpsscheme and hostname. This had been changed to only include the path and query string.- HTTP status code for overlong paths: previously, overlong file paths led to 500 Internal Server Error status code to be returned. This has been changed to return 400 Bad Request instead.
- Static file serving symlink settings fix: previously, symlink mode (follow or not) was not properly respected when serving precompressed static files.
Ferron 3 is approaching stabilization
With the core engine now demonstrating high performance and stability, the focus of the remaining beta cycle is shifting. We are freezing new features and performance optimizations for now, allowing us to focus on hardening the existing codebase and preparing for our first Release Candidate (or at least getting closer to it).
The next major phase of development will focus on building out the ecosystem, specifically focusing on custom modules, to provide the extensibility and flexibility you need to build complex, modular systems on top of Ferron.
As always, feedback, bug reports, and testing results are welcome.
Full changelog
You can see the complete changelog for Ferron 3.0.0-beta.11 in the release notes.
Try it
- Documentation: https://ferron.sh/docs/v3
- GitHub repo: https://github.com/ferronweb/ferron/tree/develop-3.x
Install Ferron 3 using the installer:
sudo bash -c "$(curl -fsSL https://get.ferron.sh/v3)"