Today, Ferron 1.x officially reaches End of Life (EOL). It will no longer receive security patches, dependency updates, or bug fixes.
This isn’t a failure story. It’s a story about learning what actually matters when you’re building infrastructure that runs in production.
The journey: from JavaScript to Rust
The Ferron project (originally code-named “Project Karpacz”) started because I wanted better performance. Before Rust, I had built an asynchronous web server in Node.js called SVR.JS. Writing a server in JavaScript taught me how HTTP parsing works, how to route requests, and how proxies behave.
But eventually, you hit a wall. Single-threaded event loops, garbage collection pauses, and high memory overhead are fine for business applications, but they don’t work for infrastructure.
To build a real web server, I needed a systems language. I turned to Rust, and that became Ferron 1.x.
What Ferron 1.x accomplished (and its fatal flaw)
As my first serious project in Rust, Ferron 1.x was very, very ambitious. I tried to build a monolithic “Swiss Army knife” that did everything. It had custom FastCGI decoders, process pre-forking pools for WSGI/ASGI (which I added later), SCGI, CGI, caching, and custom web serving all packed into the binary with feature flags.
It was memory-safe, and it was fast. But it had a major problem: it was fast, but it was blind.
When something broke, I was flying in the dark. There were no trace IDs, no way to correlate requests across services, no structured logs. I had access logs, timestamps, and prayer.
The good parts
Even in that monolithic 1.x codebase, some of the ideas that define Ferron 3 today were already there:
- Separate runtimes - I split execution into a
server-pooland alog-poolso that disk I/O from logging wouldn’t slow down network tasks. - Fast memory allocation - I used
mimallocas the global allocator from day one. - Graceful reloads - I wrote a reload loop triggered by Unix SIGHUP signals.
The crucible: failing forward to vibeio
Every big step forward came from an earlier mistake.
Early on, I tried to build an HTTP server library completely from scratch. I used Mutex<T> everywhere, ran into lock contention, watched it underperform compared to Hyper, and abandoned it.
But that failure taught me what I needed to know to build vibeio and vibeio-http later.
[Node.js SVR.JS] ──► [Ferron 1.x monolith] ──► [mutex-heavy HTTP failure]
│
[Ferron 3 edge proxy] ◄── [vibeio-http core engine] ◄───┘When I built vibeio, I wanted a high-performance, cross-platform runtime that pins execution to specific CPU cores, maximizes cache locality, and avoids context-switching overhead. I wanted to replace the monoio async runtime that I used for Ferron 2, which wasn’t being actively maintained.
I built vibeio with AI coding agents early on, but it wasn’t “vibe coding.” It required a lot of manual work—fixing io_uring use-after-free bugs that caused silent memory corruption, tracking down macOS zombie process hangs in tests, and debugging Windows IOCP ConnectEx issues with unbound UDP sockets.
The evolution: 1.x vs. 2.x vs. 3.x
The project descriptions show how my thinking changed:
- Ferron 1.x: “A fast, memory-safe web server written in Rust.” (focus: learning Rust)
- Ferron 2.x (current stable): “A fast, modern, and easily configurable web server with automatic TLS.” (focus: features)
- Ferron 3.x (beta): “A fast, modern web server built for production debugging.” (focus: observability)
Ferron 2
Ferron 2 is our current stable version. It completely changed how the engine handles modularity. It moved away from the dense 1.x YAML format (people complained about it; there’s even a website: http://noyaml.com/) to the cleaner KDL configuration syntax, and switched to the monoio async runtime—which I forked and modified to add Windows and FreeBSD support.
Ferron 3
Ferron 3 is a complete shift in philosophy. Where 1.x was about speed, 3 is about seeing what’s happening.
We took the 1,500+ line monolithic server.rs event loop and split it into separate components using vibeio and vibeio-http, with zero-copy network frames.
| What | Ferron 1.x (old monolith) | Ferron 3.x (modern edge proxy) |
|---|---|---|
| Philosophy | Monolithic (“Make it fast”) | Observable (“Make it debuggable”) |
| Routing | Flat scans / SNI matching | TlsResolverRadixTree |
| Logging | Text dumps to disk | Native OTLP, trace IDs, grep-able spans |
| Protocols | Built-in FastCGI, WSGI, ASGI, CGI | Proxy to external runtimes |
| Performance | Multi-threaded | Core-pinned workers with zero-copy sendfile |
| Config | YAML | ferron.conf |
What this means for users
If you’re still running Ferron 1.x in production:
- No more support - the 1.x branches won’t be monitored or updated anymore.
- What to do - for stable deployments, migrate to Ferron 2. If you want modern observability and tracing, try the Ferron 3 beta.
Ferron 3 is growing; it’s being packaged for Ravenports, patched for DragonFlyBSD, and used in production by homelabbers and small operators.
Thank you
To everyone who deployed, tested, and broke Ferron 1.x: thank you. You helped me realize that as systems get bigger, developers don’t just want a faster black box. They want to see what’s actually happening with their requests.
Ferron 1.x was the chrysalis. Let it rest. Time to move forward.