Ferrix Banner

Ferrix Official Documentation

Welcome to the official documentation for Ferrix โ€” a high-performance web server and reverse proxy inspired by Nginx, built in Rust (Tokio, Hyper, Axum), featuring a modern React Web Dashboard and a RESTful Control Plane API for zero-downtime hot reloading.


๐Ÿ“š Table of Contents

  1. 01. Overview and Comparison

    • What Ferrix is and project motivation.
    • Architectural comparison with Nginx and Caddy.
    • Memory safety and concurrency advantages of Rust.
  2. 02. Installation and Execution

    • System prerequisites.
    • Building the core proxy (Rust) and dashboard (React).
    • Running via CLI, command flags, and background daemon execution.
    • Containerization with Docker.
  3. 03. Internal Architecture

    • Asynchronous runtime with Tokio, Hyper, and Axum.
    • Zero-Downtime Hot-Reload via atomic pointer swaps (ArcSwap).
    • Metric collector, lock-free circular log ring buffer, and Server-Sent Events (SSE).
    • Codebase structure and modularity.
  4. 04. Feature & Configuration Guide

    • ferrix.yaml configuration structure.
    • Virtual Hosts, domain matching, and prefix/regex routing rules.
    • Load balancing algorithms (Round-Robin, Weighted, Least Connections, IP Hash, Random).
    • Active health checks and automated failover (proxy_next_upstream).
    • Transparent WebSocket tunneling.
    • Static file serving and try_files for Single Page Applications (SPAs).
    • Rate limiting (Token Bucket), IP CIDR ACLs (Allow/Deny), and HTTP Basic Auth.
  5. 05. REST API Reference

    • Complete endpoint specification and SSE live stream.
    • Practical curl examples and JSON payloads.
    • CRUD management for Virtual Hosts and Upstreams via API.
  6. 06. Web Dashboard Manual

    • Overview of all views (Dashboard, Virtual Hosts, Live Requests, Upstreams, Nginx Import, Config Editor).
    • Real-time request inspection and telemetry.
    • Visual configuration management without manually editing files.
  7. 07. Glossary

    • What p50, p99, req/s, RSS/PSS, sendfile, SNI, ACME and the other terms mean.

01. Overview and Comparison

๐Ÿ“Œ What is Ferrix?

Ferrix is an asynchronous web server and reverse proxy designed to combine the efficiency and robustness of Nginx with the memory safety of Rust and the ease-of-use of a modern React Web Dashboard.

In traditional environments, tools like Nginx require manual editing of plaintext configuration files (nginx.conf), process signal reloads (nginx -s reload), and depend on third-party modules or external tools for real-time observability.

Ferrix solves this complexity by integrating into a single lightweight executable:

  1. High-Concurrency Network Engine: Built on Tokio, Hyper, and Axum, processing tens of thousands of requests per second non-blockingly.
  2. Embedded React Web Dashboard: A sophisticated administrative interface with dark mode, live traffic charts, latency percentiles, and real-time request inspection.
  3. RESTful Management API: Full programmatic control for CI/CD pipelines, automation scripts, and cluster orchestrators.
  4. Zero-Downtime In-Memory Hot Reloading: Route, upstream, and security updates are applied atomically via pointer swaps (ArcSwap), without dropping a single active connection or closing listening sockets.

โš–๏ธ Comparison: Ferrix vs. Nginx vs. Caddy

FeatureNginxCaddyFerrix
Base LanguageCGoRust (Tokio / Hyper / Axum)
Memory SafetyVulnerable to buffer overflows / leaksSafe (with GC pauses)Zero-Cost Abstractions / No Garbage Collector
Native Web DashboardโŒ No (requires separate Nginx Proxy Manager)โŒ Noโœ… Yes (React SPA embedded in binary)
REST API for Dynamic Configโš ๏ธ Paid Nginx Plus onlyโœ… Yesโœ… Yes (Native and open-source)
Real-time Live Request Telemetryโš ๏ธ Requires exporter + Prometheus/Grafanaโš ๏ธ Requires exportersโœ… Yes (Real-time SSE stream built-in)
WebSocket Proxyingโœ… Yes (proxy_set_header Upgrade)โœ… Yesโœ… Yes (Transparent full-duplex tunnel)
Load Balancing & Failoverโœ… Yesโœ… Yesโœ… Yes (5 algorithms + automated failover)
Active Health Checksโš ๏ธ Paid Nginx Plus onlyโœ… Yesโœ… Yes (Native background health checker)
SPA Fallback (try_files)โœ… Yesโœ… Yesโœ… Yes (Native directive)
Distribution FormatBinary + dynamic modulesSingle binarySingle standalone binary (with bundled UI)

๐Ÿ›ก๏ธ Advantages of Building in Rust

  1. Memory Safety without a Garbage Collector (GC):
    • Unlike Go (used in Caddy and Traefik), Rust has no runtime garbage collection pauses (stop-the-world). This guarantees rock-solid p99 tail latencies and predictable memory usage under heavy load.
  2. Compile-Time Concurrency Safety:
    • The Rust compiler guarantees thread safety and prevents data races through its strict ownership and borrowing system.
  3. Zero-Copy Asynchronous I/O with Tokio:
    • Data streams and static file transfers operate efficiently without unnecessary memory allocations or buffer copying.

02. Installation and Execution

This guide explains how to compile, run, and operate Ferrix in both development and production environments.


๐Ÿ“‹ Prerequisites

  • Operating System: Linux, macOS, or Windows.
  • Rust Toolchain: Stable release (1.75 or later).
    curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
    
  • Node.js & npm (only required if modifying or rebuilding the React dashboard):
    • Node.js 18+ (Node.js 20+ recommended).

๐Ÿ”จ Building the Project

Ferrix includes the compiled React dashboard assets (HTML, CSS, JavaScript) directly into the final executable. This allows you to distribute a single self-contained binary with no runtime dependencies.

Step 1: Build the React Frontend

cd frontend
npm install
npm run build
cd ..

This compiles static assets into frontend/dist/.

Step 2: Build the Core Proxy in Rust

For development:

cargo build

Binary location: target/debug/ferrix.

For production (maximum optimizations and performance):

cargo build --release

Optimized binary location: target/release/ferrix.


Optional modules (smaller builds)

Like nginx's --with-* build options, some modules can be left out at compile time. All of them except http3 are built by default:

FeatureWhat it adds
http2HTTP/2 for clients (over TLS via ALPN, and h2c)
http3HTTP/3 over QUIC on TLS ports (served when http.protocols lists http3). Not in the default build
brotliBrotli response compression (gzip is always available)
zstdzstd response compression
acmeAutomatic Let's Encrypt certificates (tls.acme)
adminAdmin REST API, live request stream and the web dashboard (admin:)
nginx-importferrix import and the dashboard's "Import nginx" page
# Default: every module except HTTP/3
cargo build --release
# Minimal: HTTP/1.1, gzip, certificates from files, configured only through
# ferrix.yaml + `ferrix reload` (no admin API).
cargo build --release --no-default-features
# Pick and choose
cargo build --release --no-default-features --features "http2,brotli"
# Everything plus HTTP/3
cargo build --release --features http3

A configuration that needs a missing module (for example tls.acme: true without acme) is rejected by ferrix -t with an explanation; a client asking for an encoding that isn't built in simply gets one that is.

๐Ÿ“ฆ Installing on Linux

Quick install (any distribution)

curl -fsSL https://arthurlunkes.github.io/ferrix/install.sh | sh

The installer uses your system's own package manager, so updates and removal work the usual way:

SystemWhat it does
Debian, Ubuntu and derivativesAdds the Ferrix APT repository and runs apt install ferrix; later versions arrive with apt upgrade
Fedora, RHEL, openSUSEInstalls the release .rpm
Arch and derivativesInstalls the release .pkg.tar.zst
Anything elseInstalls the binary in /usr/local/bin and a systemd unit

Every download is checked against the release's SHA256SUMS. FERRIX_VERSION=0.2.0 installs a given version and FERRIX_METHOD=binary skips the package manager:

curl -fsSL https://arthurlunkes.github.io/ferrix/install.sh | FERRIX_VERSION=0.2.0 sh

All packages are built from the same static (musl) binary, so they run on any distribution regardless of its glibc version, on x86_64 and arm64.

Debian / Ubuntu: the APT repository by hand

sudo curl -fsSL https://arthurlunkes.github.io/ferrix/apt/ferrix-archive-keyring.gpg \
  -o /usr/share/keyrings/ferrix-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/ferrix-archive-keyring.gpg] https://arthurlunkes.github.io/ferrix/apt stable main" \
  | sudo tee /etc/apt/sources.list.d/ferrix.list
sudo apt update && sudo apt install ferrix

The repository is signed with the key 8D01 6191 3151 3806 7A8A 5C5B 4ABD 5DC1 460A DD45.

Fedora, RHEL, openSUSE and Arch

Download the package for your system from the latest release and install it. The .rpm is signed with the same key as the APT repository; import it first so your system can check it:

sudo rpm --import https://arthurlunkes.github.io/ferrix/apt/ferrix-archive-keyring.asc
sudo dnf install ./ferrix-0.2.0-1.x86_64.rpm          # Fedora / RHEL
sudo zypper install ./ferrix-0.2.0-1.x86_64.rpm       # openSUSE
sudo pacman -U ferrix-0.2.0-1-x86_64.pkg.tar.zst      # Arch

An AUR package (ferrix-bin) is prepared in packaging/aur/ and will be published soon.

What the package sets up

  • /usr/bin/ferrix and the web dashboard in /usr/share/ferrix/dashboard.
  • A ferrix system user and the ferrix.service unit, started right away. It runs as that user and may bind ports 80 and 443 without root.
  • /etc/ferrix/, where Ferrix writes ferrix.yaml on its first start: every option listed and commented out, plus a random admin token. Upgrades never touch it.

Because the service runs as the ferrix user, apply changes with sudo systemctl reload ferrix (or sudo ferrix reload).

๐ŸŽ Installing on macOS (Homebrew)

brew tap arthurlunkes/ferrix https://github.com/arthurlunkes/ferrix
brew install ferrix
brew services start ferrix          # run it in the background

The configuration is written on the first start to $(brew --prefix)/etc/ferrix/ferrix.yaml; brew upgrade ferrix installs new versions. The same formula works with Homebrew on Linux.

๐ŸชŸ Installing on Windows (Scoop)

scoop install https://raw.githubusercontent.com/arthurlunkes/ferrix/main/packaging/scoop/ferrix.json
ferrix run --config ferrix.yaml

scoop update ferrix installs new versions. Without Scoop, core\scripts\install.ps1 (from a checkout of this repository) installs it under C:\Program Files\Ferrix.

Switching from a source install

If Ferrix was installed earlier with ./core/scripts/install.sh (binary in /usr/local/bin, unit in /etc/systemd/system), remove those first: they take precedence over the package's files, so the old version would keep running. The configuration in /etc/ferrix is kept.

sudo systemctl disable --now ferrix
sudo rm -f /etc/systemd/system/ferrix.service /usr/local/bin/ferrix
sudo systemctl daemon-reload
curl -fsSL https://arthurlunkes.github.io/ferrix/install.sh | sh
hash -r && which ferrix        # should print /usr/bin/ferrix

The service then runs as the ferrix system user instead of your own; the package hands /etc/ferrix over to it.

Removing it

sudo apt remove ferrix        # Debian / Ubuntu
sudo dnf remove ferrix        # Fedora / RHEL
sudo pacman -R ferrix         # Arch

/etc/ferrix and its configuration are kept; delete the folder by hand if you no longer need it.

From source

git clone https://github.com/arthurlunkes/ferrix && cd ferrix
./core/scripts/install.sh            # or --core-only to skip the web dashboard
sudo systemctl enable --now ferrix

The script builds Ferrix, installs /usr/local/bin/ferrix and the ferrix.service unit, which runs as your user and may bind ports 80 and 443 without root.

Checking that it works

curl -I http://localhost/            # "Server: ferrix/..." and the welcome page
systemctl status ferrix
journalctl -u ferrix -f              # logs
ferrix test                          # validate the configuration

Updating an existing install

An existing /etc/ferrix/ferrix.yaml is never overwritten. To start from the new documented template, move the old file away and restart: Ferrix writes a fresh one on start.


๐Ÿš€ Execution

1. Basic Startup

./target/release/ferrix run

By default:

  • The configuration is always /etc/ferrix/ferrix.yaml (like nginx's /etc/nginx/nginx.conf), whatever directory you run ferrix from; --config <file> uses another one. If the file does not exist, Ferrix writes the documented default there, with a random admin token. Every command (run, reload, stop, status, test) uses the same file, so they always find the running server.
  • The control plane API and web dashboard listen at: http://127.0.0.1:8081.
  • The proxy listens on http://0.0.0.0:80 (and on 443 for hosts with tls:), like nginx. Ports below 1024 need root or CAP_NET_BIND_SERVICE; the systemd unit and the packages grant it. To run by hand as a normal user, either allow the binary once (sudo setcap cap_net_bind_service=+ep $(which ferrix)) or use another port: ferrix run --http-listen 0.0.0.0:8080.
  • Open http://localhost/ and you should see the Ferrix is running page.
  • The generated ferrix.yaml lists every option, commented out: uncomment what you need.

2. CLI Commands & Options

Ferrix provides an intuitive command line interface:

โšก Ferrix โ€” High-Performance Standalone Reverse Proxy & Web Server

Usage: ferrix [COMMAND] [OPTIONS]

Main commands:
  run       Start the server in foreground
  start     Start the server in background (daemon)
  stop      Stop the background server
  restart   Restart the background server
  reload    Reload configuration without dropping active connections
  status    Check if the server is currently running
  test      Test configuration file syntax (shortcut: ferrix -t)
  import    Import and convert existing nginx.conf configurations

Global options:
  -c, --config <FILE>      Configuration file path [default: /etc/ferrix/ferrix.yaml]
  -t, --test               Test configuration file syntax and exit
  -s, --signal <SIGNAL>    Send signal to Ferrix (stop, reload, restart, status)
  -h, --help               Display all detailed help options
  -V, --version            Display installed Ferrix version

Examples:

Run with a custom configuration file:

./target/release/ferrix run --config /etc/ferrix/production.json

Start Ferrix as a background daemon:

./target/release/ferrix start

Check runtime status and health:

./target/release/ferrix status

Test syntax without applying:

./target/release/ferrix -t

Master process (automatic restarts)

Like nginx, ferrix run keeps a small master process that starts the actual server as a child and restarts it within ~100 ms if it ever dies (with a growing delay if it keeps failing). ferrix reload and ferrix stop signal the master, which forwards them. Configuration errors are not retried: the server exits and so does the master.

Release builds use panic = "abort": a bug that panics ends the server process instead of unwinding, which saves ~1 MB of resident code; the master brings it back.

The master is a second process (~3โ€“4 MB). Where something else already restarts Ferrix, it's turned off with FERRIX_SUPERVISOR=0, and that's the default in the places Ferrix ships with one: the Docker images (start them with --restart unless-stopped, as the docker-compose.yml does) and the systemd unit from install.sh (Restart=on-failure).

๐Ÿงช Testing with Mock Backends

To experiment with load balancing, you can run the provided test_backends.js script:

# In terminal 1:
node test_backends.js

# Expected output:
# [Backend-1] listening on http://127.0.0.1:9001
# [Backend-2] listening on http://127.0.0.1:9002

In terminal 2, start Ferrix:

./target/release/ferrix run --config ferrix.yaml

Send requests to http://localhost/api and verify:

  1. Responses alternating between Backend-1 and Backend-2.
  2. Live metrics and request logs appearing in real time on the web dashboard at http://localhost:8081.

๐Ÿณ Running with Docker (GitHub Packages)

Official multi-architecture images (linux/amd64 and linux/arm64) are published to the GitHub Container Registry. You can choose between the ultra-lean Core image (~20 MB) or the Full All-in-One image with the Web Dashboard:

Option A: Ultra-Lean Core (~20 MB, Rust Proxy Only)

Ideal for production, Kubernetes, and minimal resource footprints:

docker run -d \
  --name ferrix \
  -p 80:80 \
  -p 8081:8081 \
  -v $(pwd)/ferrix.yaml:/etc/ferrix/ferrix.yaml \
  ghcr.io/arthurlunkes/ferrix:latest

Option B: Full, with the web dashboard on :8080

One Ferrix process serves the proxy (:80) and the admin API plus dashboard (:8080); the image has no Node.js. The first start writes ferrix.yaml into the mounted directory with a random admin token, shown in the log:

docker run -d \
  --name ferrix-full \
  -p 80:80 \
  -p 8080:8080 \
  -v $(pwd)/ferrix-data:/etc/ferrix \
  --restart unless-stopped \
  ghcr.io/arthurlunkes/ferrix:full
docker logs ferrix-full

Using Docker Compose

services:
  ferrix-core:
    image: ghcr.io/arthurlunkes/ferrix:latest
    ports:
      - "80:80"
      - "8081:8081"
    volumes:
      - ./ferrix.yaml:/etc/ferrix/ferrix.yaml

Run with:

docker compose up -d ferrix-core

03. Internal Architecture

Ferrix is engineered around modern distributed systems principles and asynchronous network I/O. This section explains the internal components and how they cooperate to deliver high throughput and zero-downtime operation.


๐Ÿ›๏ธ Structural Overview

                          โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
                          โ”‚                      FERRIX PROCESS                    โ”‚
                          โ”‚                                                        โ”‚
Incoming HTTP / WS  โ”€โ”€โ”€โ”€โ”€โ–บโ”‚  Proxy Listener (Port 80/443, configurable)             โ”‚
                          โ”‚  โ”œโ”€โ”€ Client Address Extractor (ConnectInfo)            โ”‚
                          โ”‚  โ”œโ”€โ”€ Host Header Matcher (VirtualHost Finder)          โ”‚
                          โ”‚  โ”œโ”€โ”€ Middleware Pipeline (IP ACL, RateLimit, Auth)     โ”‚
                          โ”‚  โ”œโ”€โ”€ Route Rule Prefix & Regex Matcher                 โ”‚
                          โ”‚  โ”‚   โ”œโ”€โ”€ Target: Static File Server (Zero-Copy)        โ”‚
                          โ”‚  โ”‚   โ”œโ”€โ”€ Target: HTTP Redirect (301/302/307/308)       โ”‚
                          โ”‚  โ”‚   โ””โ”€โ”€ Target: Upstream Pool (Load Balancer)         โ”‚
                          โ”‚  โ”‚       โ”œโ”€โ”€ WS Upgrade: Tokio Bidirectional Tunnel   โ”‚
                          โ”‚  โ”‚       โ””โ”€โ”€ HTTP: Hyper / Reqwest Client + Failover   โ”‚
                          โ”‚  โ”‚                                                     โ”‚
                          โ”‚  โ””โ”€โ”€ Telemetry Interceptor                             โ”‚
                          โ”‚      โ”œโ”€โ”€ Atomic Counters (2xx, 3xx, 4xx, 5xx, Bytes)   โ”‚
                          โ”‚      โ”œโ”€โ”€ Lock-Free Ring Buffer (Recent 1000 logs)      โ”‚
                          โ”‚      โ””โ”€โ”€ Broadcast Channel (tokio::sync::broadcast)    โ”‚
                          โ”‚                     โ”‚                                  โ”‚
                          โ”‚                     โ–ผ                                  โ”‚
Browser / REST API  โ”€โ”€โ”€โ”€โ”€โ–บโ”‚  Admin Listener (Port 8081 / Configurable)             โ”‚
                          โ”‚  โ”œโ”€โ”€ REST API Handlers (/status, /config, etc.)        โ”‚
                          โ”‚  โ”œโ”€โ”€ Server-Sent Events (SSE Stream)                   โ”‚
                          โ”‚  โ””โ”€โ”€ Embedded React SPA (rust-embed Assets)            โ”‚
                          โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

โšก Zero-Downtime Hot Reload via ArcSwap

In traditional Nginx, reloading configuration without stopping the server (nginx -s reload) relies on the master process spawning new worker processes and waiting for old ones to terminate active connections (graceful worker restart).

In Ferrix, taking advantage of Rust's atomic pointer primitives, we use the arc-swap crate (ArcSwap<FerrixConfig>):

  1. The runtime configuration is wrapped in an atomic pointer:
    #![allow(unused)]
    fn main() {
    pub shared_config: Arc<ArcSwap<FerrixConfig>>
    }
  2. During request processing, Tokio worker threads perform a quick lock-free load (shared_config.load()), requiring only a single atomic CPU instruction (no mutex locks, zero lock contention).
  3. When new configuration is submitted through the web dashboard or REST API:
    • The configuration is validated;
    • Persisted safely to ferrix.yaml on disk;
    • Stored atomically into the pointer via shared_config.store(Arc::new(new_config)).
  4. All incoming connections immediately see the updated routing table, while in-flight requests complete uninterrupted.
  5. Result: Zero downtime, zero dropped connections, and sub-millisecond reload latency.

๐Ÿ”„ Full-Duplex Bidirectional WebSocket Tunnel

When a client sends a request with Upgrade: websocket and Connection: Upgrade headers, Ferrix automatically initiates a protocol upgrade:

  1. The initial handshake is processed via Axum's WebSocketUpgrade.
  2. Concurrently, Ferrix establishes an asynchronous connection to the target backend chosen by the load balancing algorithm using tokio_tungstenite::connect_async.
  3. Once connected, two concurrent asynchronous tasks bridge the client and upstream:
    • Task A: reads frames from the client and forwards them to the backend.
    • Task B: reads frames from the backend and relays them to the client.
  4. When either peer sends a Close frame, the tunnel terminates cleanly.

๐Ÿ“Š Telemetry, Metrics, and Server-Sent Events (SSE)

The telemetry subsystem is designed to introduce zero overhead to the critical request path:

  • Atomic Counters: total_requests, active_connections, status_2xx, status_3xx, status_4xx, status_5xx, and total_bytes_sent utilize AtomicU64 with relaxed ordering (Ordering::Relaxed).
  • Latency Percentiles (p50, p95, p99): Computed over a circular ring buffer tracking the last 1,000 requests.
  • Real-Time Streaming: Each finished request emits a RequestLog to an asynchronous broadcast channel (tokio::sync::broadcast::Sender).
  • SSE Endpoint (/api/logs/stream): The React dashboard consumes this event stream over EventSource, updating the live requests table without polling.

๐Ÿ“ Source Code Organization

The core follows the same layering as nginx's src/ directory: a base layer with no HTTP in it, the event loop, the HTTP server split into small modules (one per nginx module it mirrors), and the OS-specific bits apart.

core/src/
โ”œโ”€โ”€ main.rs              process bootstrap, CLI, signals, worker threads  (nginx.c, os/unix/ngx_process*)
โ”œโ”€โ”€ config/              JSON config model, validation, hot-reload store   (ngx_conf_file + each module's directives)
โ”œโ”€โ”€ event/
โ”‚   โ”œโ”€โ”€ accept.rs        accept loop, worker_connections, hyper handoff    (event/ngx_event_accept.c)
โ”‚   โ””โ”€โ”€ clock.rs         cached time + per-worker coarse timers           (core/ngx_times.c, event/ngx_event_timer.c)
โ”œโ”€โ”€ os/                  RLIMIT_NOFILE, CPU affinity                       (os/unix)
โ”œโ”€โ”€ tls/
โ”‚   โ”œโ”€โ”€ mod.rs           which ports speak TLS
โ”‚   โ”œโ”€โ”€ sni.rs           certificate per SNI name, rustls acceptor         (event/ngx_event_openssl.c, ngx_http_ssl_module)
โ”‚   โ””โ”€โ”€ acme.rs          Let's Encrypt certificates
โ”œโ”€โ”€ http/
โ”‚   โ”œโ”€โ”€ mod.rs           request lifecycle: vhost โ†’ route โ†’ handler โ†’ filters   (ngx_http_core_module)
โ”‚   โ”œโ”€โ”€ server.rs        Ferrix's own HTTP/1.1 parser/writer, sendfile         (ngx_http_request.c, ngx_http_parse.c)
โ”‚   โ”œโ”€โ”€ matcher.rs       server_name and location matching                     (ngx_http_core_module locations)
โ”‚   โ”œโ”€โ”€ proxy.rs         reverse proxy handler, failover                       (ngx_http_proxy_module)
โ”‚   โ”œโ”€โ”€ static_files.rs  static files, ETag/304, ranges, precompressed copies  (static, not_modified, range, gzip_static)
โ”‚   โ”œโ”€โ”€ cache.rs         response cache                                        (proxy_cache)
โ”‚   โ”œโ”€โ”€ access.rs        allow/deny + basic auth                               (access, auth_basic)
โ”‚   โ”œโ”€โ”€ limit_req.rs     rate limiting                                         (limit_req)
โ”‚   โ”œโ”€โ”€ limit_conn.rs    connection limiting                                   (limit_conn)
โ”‚   โ”œโ”€โ”€ realip.rs        client IP from trusted proxies                        (realip)
โ”‚   โ”œโ”€โ”€ websocket.rs     WebSocket tunnel                                      (proxy upgrade)
โ”‚   โ”œโ”€โ”€ default_page.rs  "Welcome to Ferrix" page                              (default site)
โ”‚   โ”œโ”€โ”€ filters/         response filter chain, run in order:                  (header/body filters)
โ”‚   โ”‚                    compress โ†’ headers โ†’ cors โ†’ request_id, plus error_page
โ”‚   โ””โ”€โ”€ upstream/
โ”‚       โ”œโ”€โ”€ mod.rs       client to upstreams (timeouts, retries on stale conns) (ngx_http_upstream.c)
โ”‚       โ”œโ”€โ”€ h1.rs        HTTP/1.1 client with a per-thread connection pool      (upstream_keepalive)
โ”‚       โ”œโ”€โ”€ balancer.rs  round robin, weighted, least_conn, ip_hash, random     (upstream_*_module)
โ”‚       โ””โ”€โ”€ hash.rs      consistent hashing (ketama)                            (upstream_hash)
โ”œโ”€โ”€ stream.rs            TCP/UDP proxy                                         (src/stream/)
โ”œโ”€โ”€ health/              active health checks
โ”œโ”€โ”€ metrics/             counters, latency histogram, request log, Prometheus  (stub_status, much extended)
โ”œโ”€โ”€ admin/               control-plane REST API and SSE
โ”œโ”€โ”€ net/                 CIDR matching
โ””โ”€โ”€ nginx/               nginx.conf importer (lexer, parser, converter, report)

Adding an HTTP feature: a new response filter goes in http/filters/ and is called from filters::run in the right order; a new content handler gets its own file under http/ and a RouteTargetType; a new balancing algorithm goes in http/upstream/balancer.rs.

04. Configuration & Features Guide

Ferrix's configuration file is written in YAML (ferrix.yaml by default), easy to edit by hand, review in Git or generate from infrastructure-as-code tools. The REST API and the dashboard exchange the same structure as JSON. A ferrix.json from an older version is still read; ferrix convert-config ferrix.json ferrix.yaml converts it.


๐Ÿ“„ General Structure of ferrix.yaml

admin:
  listen: 127.0.0.1:8081
http:
  listen: 0.0.0.0:80
  worker_threads: 4
upstreams:
  # ... see "Upstreams and Load Balancing"
vhosts:
  # ... see "Virtual Hosts"

๐ŸŒ Virtual Hosts (Sites)

Each Virtual Host represents a server block (equivalent to an Nginx server { ... } block).

id: main-vhost
name: My Web Application
server_names: [mydomain.com, www.mydomain.com, localhost]
listen_port: 443        # default: 443 with tls, 80 without
headers:
  X-Powered-By: Ferrix/0.1.0
  X-Frame-Options: SAMEORIGIN
  X-Content-Type-Options: nosniff
allowed_ips: []
denied_ips: [192.168.1.100]
basic_auth:
  username: admin
  password: secure_password_here
rate_limit:
  requests_per_second: 50
  burst: 100
routes:
  # ... see "Routing Rules"

Virtual Host Properties:

  • id: Unique identifier (used in REST API URLs).
  • name: Friendly human-readable name displayed on the dashboard.
  • server_names: List of hostnames or IPs accepted in the Host header. Supports * as a catch-all for all domains.
  • listen_port: Inbound traffic listening port.
  • headers: HTTP headers injected into all responses served by this virtual host.
  • allowed_ips: List of authorized IPs. If empty, all IPs are allowed (except explicitly denied ones).
  • denied_ips: Blocked IP addresses (returns 403 Forbidden).
  • basic_auth (optional): Protects all routes with HTTP Basic Authentication.
  • rate_limit (optional): Global host rate limit applied per client IP.

๐Ÿ›ฃ๏ธ Routing Rules (Routes)

Inside each Virtual Host, you configure path routing rules (routes), equivalent to Nginx location blocks:

1. Reverse Proxy Route

Forwards traffic to a backend server pool:

id: api-route
path_prefix: /api
strip_prefix: false
target_type: proxy_upstream
upstream_name: my_backend_cluster
websocket_enabled: true
proxy_set_headers:
  Host: $host
  X-Real-IP: $remote_addr
  X-Forwarded-For: $proxy_add_x_forwarded_for
  X-Forwarded-Proto: $scheme
add_headers:
  Access-Control-Allow-Origin: '*'
  • strip_prefix: If true, strips /api from the URI path before forwarding to the backend.
  • websocket_enabled: Enables long-lived bidirectional WebSocket tunneling.
  • proxy_set_headers: Custom upstream headers with special variable substitution ($host, $remote_addr, $proxy_add_x_forwarded_for, $scheme).

2. Static Files Route with SPA Fallback

Serves local disk files and routes missing paths to index.html:

id: frontend-route
path_prefix: /
strip_prefix: false
target_type: static_files
static_root: ./frontend/dist
try_files: $uri $uri/ /index.html
websocket_enabled: false
  • static_root: Local disk path where files are hosted.
  • try_files: Checks for the exact file ($uri), then directory index ($uri/), falling back to (/index.html). Tailor-made for React Router, Vue Router, etc.

3. HTTP Redirect Route

Redirects the browser to an alternate URL:

id: docs-route
path_prefix: /docs
strip_prefix: false
target_type: redirect
redirect_url: https://github.com/my-project
redirect_code: 301

โš–๏ธ Upstreams and Load Balancing

Upstreams define pools of backend servers that share incoming workload.

upstreams:
  my_backend_cluster:
    name: my_backend_cluster
    algorithm: round_robin
    targets:
      - url: http://10.0.0.1:3000
        weight: 3
        max_fails: 3
        fail_timeout_secs: 10
      - url: http://10.0.0.2:3000
        weight: 1
        max_fails: 3
        fail_timeout_secs: 10
    health_check:
      path: /health
      interval_secs: 5
      timeout_secs: 2
      expected_status: 200

Supported Balancing Algorithms (algorithm):

  1. round_robin: Cycles sequentially through healthy targets.
  2. weighted_round_robin: Distributes proportionally to each server's configured weight (e.g., weight 3 receives 3x more requests than weight 1).
  3. least_conn: Routes to the target with the fewest active concurrent connections at request time.
  4. ip_hash: Hashes client IP to ensure continuous affinity with the same backend (sticky session).
  5. random: Uniform random distribution among healthy nodes.
  6. consistent_hash: Ketama-style consistent hashing on hash_key (nginx hash ... consistent). Adding or removing a server only remaps the keys that belonged to it. hash_key accepts $remote_addr (default), $request_uri, $uri, $host, $http_<header>, $cookie_<name> and $arg_<name>.
upstreams:
  api:
    name: api
    algorithm: consistent_hash
    hash_key: $request_uri
    targets:
      - url: http://10.0.0.1:8080
      - url: http://10.0.0.2:8080

Automatic Failover (proxy_next_upstream):

If a pool node fails (connection error, timeout, or HTTP 502/504 status code):

  1. Ferrix increments the node's consecutive failure counter.
  2. Immediately tries the next available target in the pool without dropping the client request.
  3. Once consecutive failures reach max_fails, the node is temporarily marked UNHEALTHY until active background health checks confirm recovery.

๐Ÿ“Š Metrics (opt-in)

Metrics are off by default, like nginx's stub_status. While disabled, the proxy does no per-request bookkeeping at all: no counters, no in-memory request log, no byte counting. Turn them on when you want the dashboard charts, Live Requests or Prometheus:

metrics:
  enabled: true

When enabled:

  • GET /metrics on the admin port serves Prometheus text format (ferrix_requests_total, ferrix_request_duration_seconds histogram, ferrix_response_bytes_total, ferrix_active_requests, ferrix_upstream_up). It uses the admin authentication unless admin.metrics_public is true.
  • /api/metrics, /api/logs and /api/logs/stream feed the dashboard.

Turning metrics on costs roughly 4โ€“9% of throughput in the benchmarks. The control plane itself can be switched off with admin: { enabled: false }; Ferrix is then configured only through ferrix.yaml and ferrix reload. The web dashboard is optional: it's served by the admin server from admin.dashboard_dir when that is set, with no extra process.

๐Ÿ—œ๏ธ Compression

http:
  compression:
    enabled: true
    level: default
    min_length: 1024
    algorithms:
      - zstd
      - br
      - gzip
    types:
      - text/*
      - application/json
      - application/javascript
      - image/svg+xml

The encoding is negotiated from Accept-Encoding in the order of algorithms. level is fastest, default or best. A route can override the global switch with compression: true|false. Static files are compressed once and kept in memory (like gzip_static), so repeated requests cost no CPU. Proxied responses are compressed on the fly.

๐Ÿ“ Static files: caching and ranges

Small files are kept in memory so they're served without touching the disk; larger ones are sent with sendfile straight from the kernel's page cache. Both limits are configurable:

http:
  static_cache:
    max_file_bytes: 1048576      # files up to 1 MB are cached (0 disables the cache)
    max_total_bytes: 67108864    # the cache uses at most 64 MB

Static responses carry ETag and Last-Modified. If-None-Match and If-Modified-Since are answered with 304 Not Modified, and single Range requests are answered with 206 Partial Content (or 416).

๐ŸŒ CORS

cors:
  allow_origins:
    - https://app.example.com
  allow_methods:
    - GET
    - POST
  allow_headers:
    - Authorization
    - Content-Type
  expose_headers:
    - X-Request-Id
  allow_credentials: true
  max_age_secs: 600

Set this on a route. Preflight requests (OPTIONS with Access-Control-Request-Method) are answered by Ferrix without reaching the upstream. "*" in allow_origins allows any origin. With credentials enabled, the origin is echoed back instead of *.

๐Ÿงพ Custom error pages and request IDs

error_pages:
  '404': /var/www/errors/404.html
  5xx: /var/www/errors/50x.html

error_pages belongs to a virtual host and replaces the body of the errors Ferrix generates itself, while keeping the status code. Error responses that come from an upstream pass through untouched, like nginx without proxy_intercept_errors.

http: { request_id: true } adds an X-Request-Id to the upstream request and to the response. An ID already sent by the client is kept.

๐ŸŒ HTTP versions: HTTP/1.1, HTTP/2 and HTTP/3

http:
  protocols: [http1, http2, http3]   # default: [http1, http2]

protocols lists the HTTP versions Ferrix serves to clients; leave one out and it is refused:

ValueWhere it runs
http1HTTP/1.0 and 1.1, on plain and TLS ports
http2Over TLS (negotiated with ALPN) and, on plain ports, for clients that start with it (h2c)
http3Over QUIC (UDP) on the same port number as every TLS port, with the same certificates. TCP responses carry Alt-Svc: h3=":443" so browsers switch to it

Notes:

  • http3 needs at least one TLS virtual host, and either http1 or http2 must stay on: clients discover HTTP/3 through a first TCP connection.
  • Open the UDP port in your firewall (and publish it in Docker: -p 443:443/udp).
  • Requests are routed, filtered and proxied the same way whatever the version; upstreams are always reached over HTTP/1.1, like nginx.
  • Changing protocols needs a restart (ferrix restart), since it changes the listeners.
  • HTTP/3 is not in the default build, so the packages and Docker images don't include it: build Ferrix with cargo build --release --features http3. Even when unused it would add about 1 MB to the binary and 0.7 MB of memory, and ~5 MB once in use. Without it, http3 in protocols is ignored with a warning.

๐Ÿ” HTTPS: several certificates per port (SNI) and Let's Encrypt

Any number of virtual hosts can share a TLS port. The certificate is chosen per connection from the SNI name: exact server_names first, then wildcards (*.example.com), then the first TLS host on the port. Certificates are re-read when the configuration changes, so a renewal only needs ferrix reload.

For automatic certificates, use acme instead of file paths:

acme:
  email: admin@example.com
  storage_dir: ./acme
vhosts:
  - id: site
    server_names: [example.com, www.example.com]
    listen_port: 443
    tls:
      acme: true
    routes:
      # ...

Ferrix answers the HTTP-01 challenge on its plain-HTTP ports, so port 80 must reach it. Certificates are stored in storage_dir and renewed renew_before_days (default 30) before they expire. To test against Let's Encrypt staging, set "directory_url": "https://acme-staging-v02.api.letsencrypt.org/directory". Wildcard names can't be validated over HTTP-01 and are skipped.

๐Ÿ”Œ TCP/UDP streams

Layer-4 proxying, like nginx's stream {} block. It works for databases, Redis, MQTT, DNS and anything else that runs over TCP or UDP:

upstreams:
  pg:
    name: pg
    algorithm: least_conn
    targets:
      - url: tcp://10.0.0.5:5432
      - url: tcp://10.0.0.6:5432
streams:
  - id: postgres
    listen: 0.0.0.0:5432
    protocol: tcp
    upstream: pg
    connect_timeout_ms: 5000
    idle_timeout_ms: 600000

Streams use the pool's balancing algorithm and passive health marking. ferrix import converts nginx stream {} blocks automatically. Streams start with the process; adding a new one needs a restart.

โš™๏ธ Workers, CPU, open files and the default page (nginx-style settings)

These live in the http section and mirror nginx's main-context directives:

http:
  worker_threads: 4
  worker_cpu_affinity: auto
  worker_connections: 4096
  worker_rlimit_nofile: 65535
  server_tokens: true
  default_page: true
Ferrix (http.*)nginxWhat it does
worker_threadsworker_processesWorker threads serving the proxy ports. Default: one per CPU core available to the process.
worker_cpu_affinityworker_cpu_affinity"off" (default), "auto" (worker N on the N-th allowed core) or a list of core ids such as [0, 2, 4, 6]. Linux only.
worker_connectionsworker_connectionsMaximum simultaneous client connections per worker (default 4096). A worker at the limit stops accepting until a connection closes; new connections wait in the kernel's listen queue.
worker_rlimit_nofileworker_rlimit_nofileRaises the open-file limit (RLIMIT_NOFILE) at startup. Every connection and served file uses one descriptor, so set it above worker_threads ร— worker_connections. Going above the system's hard limit needs root.
server_tokensserver_tokenstrue (default) sends Server: ferrix/<version> and shows the version on the default page; false sends just Server: ferrix.
default_pagethe default sitetrue (default) answers requests that match no virtual host (or a host with no routes yet) with a "Welcome to Ferrix" page; false answers a plain 404.

ferrix import converts the matching nginx directives (worker_processes, worker_cpu_affinity, events { worker_connections }, worker_rlimit_nofile, server_tokens) automatically.

05. REST API Reference

Ferrix exposes a modern REST API on its administrative port (admin.listen, default 127.0.0.1:8081; 8080 in the Docker "full" image) for telemetry metrics, historical request audits, real-time logs, and dynamic zero-downtime configuration management.


๐Ÿ“ก Endpoints Summary

MethodPathDescription
GET/api/statusHealth, process info, version, and uptime
GET/api/metricsReal-time telemetry snapshot (RPS, latencies, counters)
GET/api/logsHistorical request logs with filters and pagination
GET/api/logs/streamLive event stream via Server-Sent Events (SSE)
GET/api/configReturns active global configuration
POST/api/configApplies new configuration with zero-downtime hot-reload
GET/api/config/yamlActive configuration as YAML text (what ferrix.yaml holds)
POST/api/config/yamlApplies YAML text: parsed, validated, saved, hot-reloaded
POST/api/config/yaml/validateValidates YAML text without applying it
GET/api/vhostsLists all Virtual Hosts
POST/api/vhostsCreates or updates a Virtual Host
DELETE/api/vhosts/:idDeletes a Virtual Host
GET/api/upstreamsLists all Upstreams and health status of targets
POST/api/upstreamsCreates or updates an Upstream pool
DELETE/api/upstreams/:nameRemoves an Upstream pool

1. Server Status

GET /api/status

Returns process metadata, configuration summary, and active uptime.

Example Response:

{
  "name": "Ferrix",
  "version": "0.1.0",
  "uptime_seconds": 1245,
  "start_time_utc": "2026-09-08T16:38:54.332504764Z",
  "admin_listen": "127.0.0.1:8081",
  "proxy_listen": "0.0.0.0:80",
  "total_vhosts": 2,
  "total_upstreams": 1
}

2. Telemetry & Metrics

GET /api/metrics

Returns throughput, active connection metrics, status code aggregates, and latency percentiles.

Example Response:

{
  "uptime_seconds": 1245,
  "total_requests": 84210,
  "active_connections": 14,
  "requests_per_second": 320.5,
  "status_2xx": 81500,
  "status_3xx": 2100,
  "status_4xx": 580,
  "status_5xx": 30,
  "total_bytes_sent": 584920194,
  "avg_latency_ms": 1.45,
  "p95_latency_ms": 3.2,
  "p99_latency_ms": 6.8
}

3. Request Logs

GET /api/logs

Query the recent ring-buffer of processed requests.

Query Parameters:

  • limit (optional, default 100, max 500): Number of entries to return.
  • status (optional): Exact status code or range prefix (e.g., 200 or 400 for 4xx errors).
  • q (optional): Text search matching URI, Client IP, or Host header.

Example Request:

curl "http://localhost:8081/api/logs?limit=10&status=400&q=api"

Example Response:

[
  {
    "id": "601fa7db-3b13-4709-b4d4-1a202426a4bf",
    "timestamp": "2026-09-08T17:12:16.230268210Z",
    "client_ip": "192.168.1.50",
    "method": "POST",
    "uri": "/api/users",
    "host": "mydomain.com",
    "status": 200,
    "latency_ms": 2.14,
    "bytes_sent": 420,
    "upstream": "http://127.0.0.1:9001",
    "user_agent": "Mozilla/5.0 ...",
    "is_websocket": false,
    "error": null
  }
]

GET /api/logs/stream (SSE)

Real-time push channel streaming request events whenever a proxy transaction finishes.

JavaScript EventSource Example:

const es = new EventSource("http://localhost:8081/api/logs/stream");
es.addEventListener("request", (event) => {
  const log = JSON.parse(event.data);
  console.log(
    `[${log.method}] ${log.uri} -> ${log.status} (${log.latency_ms}ms)`,
  );
});

4. Configuration Management & Hot-Reload

GET / POST /api/config/yaml

The same configuration as /api/config, but as the YAML text stored in ferrix.yaml (Content-Type: application/yaml). The dashboard's config editor uses these. POST answers with the same JSON result as POST /api/config, or 400 with the YAML error.

curl -H "Authorization: Bearer $TOKEN" http://localhost:8081/api/config/yaml > ferrix.yaml
# edit ferrix.yaml, then:
curl -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/yaml" \
     --data-binary @ferrix.yaml http://localhost:8081/api/config/yaml

GET /api/config

Retrieves the currently active JSON configuration.

POST /api/config

Overwrites global configuration, persists to disk, and executes atomic hot-reload.

curl Example:

curl -X POST http://localhost:8081/api/config \
  -H "Content-Type: application/json" \
  -d @new_config.json

5. Virtual Hosts CRUD

POST /api/vhosts

Creates or updates a Virtual Host. If id already exists, it is updated in place; otherwise, a new host is appended.

curl -X POST http://localhost:8081/api/vhosts \
  -H "Content-Type: application/json" \
  -d '{
    "id": "new-vhost",
    "name": "Auth Gateway Service",
    "server_names": ["auth.mydomain.com"],
    "listen_port": 80,
    "headers": {
      "X-Service": "Auth-Gateway"
    },
    "allowed_ips": [],
    "denied_ips": [],
    "routes": [
      {
        "id": "auth-route",
        "path_prefix": "/",
        "strip_prefix": false,
        "target_type": "proxy_upstream",
        "upstream_name": "default_backend",
        "websocket_enabled": true,
        "proxy_set_headers": { "Host": "$host" },
        "add_headers": {}
      }
    ]
  }'

DELETE /api/vhosts/:id

Deletes a Virtual Host and reloads routing tables.

curl -X DELETE http://localhost:8081/api/vhosts/new-vhost

06. Web Dashboard Guide (React UI)

Ferrix comes equipped with a comprehensive graphical dashboard built with React 19, Vite, and styled with Tailwind CSS in a modern dark theme.

The dashboard is a static React build served by Ferrix itself from admin.dashboard_dir, on the admin port, so production needs no Node.js or separate web server environments to run the web UI.

Access the dashboard by navigating to: ๐Ÿ‘‰ http://localhost:8081 (the admin.listen address; 8080 in the Docker "full" image)

Enable it with:

metrics:
  enabled: true
admin:
  dashboard_dir: /usr/share/ferrix/dashboard   # or frontend/dist in a checkout

๐Ÿงญ Top Navigation Bar (Navbar)

At the top of every view, the navigation bar displays:

  • Logo and Version: Visual Ferrix branding and current running binary version.
  • Navigation Tabs:
    • Dashboard: High-level operational overview and telemetry graphs.
    • Virtual Hosts: Manage site configs, domains, and route rules.
    • Live Requests: Live streaming and inspection of HTTP/WebSocket traffic.
    • Upstreams: Backend pool health checks and load balancing.
    • Config Editor: JSON configuration editor with validation and instant hot-reload.
    • Import Nginx: Convert Nginx .conf files directly into Ferrix configurations.
  • SSE Connection Status:
    • A pulsing green indicator confirms that Server-Sent Events are active and streaming real-time metrics.
  • Uptime & Ports:
    • Displays the active proxy port and process uptime.

๐Ÿ“ˆ 1. Dashboard View

The primary dashboard provides immediate operational visibility into proxy infrastructure:

  1. KPI Metric Cards:
    • Total Requests: Cumulative requests served and active concurrent open connections.
    • Throughput (RPS): Rolling requests per second.
    • Avg Latency: Average response time in milliseconds along with p95 latency.
    • Error Rate: Percentage of 4xx/5xx responses relative to total volume.
  2. Real-time Area Chart:
    • Renders second-by-second throughput curves (RPS in green) and response latencies (in blue).
  3. HTTP Status Distribution:
    • Donut chart categorizing responses into 2xx (Success), 3xx (Redirect), 4xx (Client Error), and 5xx (Server Error).
  4. Recent Activity Feed:
    • Compact table listing the last 6 completed requests.

โšก 2. Live Requests (Real-Time Inspection)

The Live Requests screen provides deep observability and debugging capabilities for incoming traffic:

  • Instant Event Stream: Every request handled by Ferrix streams live into the view without reloading.
  • Status Code Filters:
    • Quick filters for 2xx (Success), 3xx (Redirects), 4xx (Client errors), or 5xx (Server errors).
  • HTTP Method Filters:
    • Filter by GET, POST, PUT, DELETE, or WS (WebSocket).
  • Live Search:
    • Search by request path, client IP, host, or upstream backend.
  • Stream Controls:
    • "Pause Stream": Freezes scrolling to inspect a specific request without incoming events displacing the table.
    • "Resume Stream": Resumes real-time streaming.
    • "Clear": Flushes the local view history.

๐Ÿ” Request Details Modal

Clicking on any row opens a comprehensive inspection modal displaying:

  • HTTP status code and method;
  • Exact response time in milliseconds;
  • Transferred payload size (bytes);
  • Connection type (HTTP/1.1 or WebSocket);
  • Client IP address and Host header;
  • Upstream target address that processed the request;
  • Full User-Agent string;
  • Detailed error messages (for responses >= 400).

๐ŸŒ 3. Virtual Hosts View

Manage domains and routing topologies through an intuitive graphical editor:

  • Site List: View all configured domains, listen ports, and route definitions.
  • Create / Edit Virtual Host:
    • Configure hostnames (e.g. app.mydomain.com, api.mydomain.com, or wildcard *).
    • Attach multiple routes with prefix patterns (e.g. /, /api, /assets).
    • Select route target types:
      • Proxy Upstream: Select load-balanced backend pools and toggle WebSocket tunneling.
      • Static Files: Serve local disk folders and configure try_files SPA fallbacks.
      • Redirect: Set up permanent (301) or temporary (302) redirections.
    • Changes take effect immediately via zero-downtime Hot-Reload.

๐Ÿ–ฅ๏ธ 4. Upstreams View

Monitor availability and performance across backend server targets:

  • Upstream Pools Overview: Inspect cluster nodes, healthy counts, and active balancing algorithms.
  • Per-Target Health Monitoring:
    • Visual indicators for healthy (Healthy) vs failing (Unhealthy) nodes.
    • Response latency measured by background health checks in milliseconds.
    • Consecutive failure counters and configured error thresholds.
  • Health Check Configuration:
    • Configure probe endpoints (e.g. /health), probe intervals, and expected HTTP status codes.

๐Ÿ“ 5. Config Editor View

For engineers who prefer editing configurations in code:

  • Syntax-Highlighted Editor: Full ferrix.yaml with line numbers and linting.
  • "Validate & Hot-Reload": Validates JSON syntax and atomically updates the running server.
  • Export & Import:
    • Download active configuration backups (Export JSON).
    • Upload previously saved configurations (Import JSON).
  • "Reset": Reverts uncommitted changes back to current active memory state.

07. Glossary

Short definitions of the terms and abbreviations used in the README, the benchmarks and the rest of this documentation.

Performance and benchmarks

TermMeaning
req/sRequests per second: how many HTTP requests the server completed per second during the test (throughput). Higher is better.
LatencyTime from sending a request until the full response arrives. Lower is better.
p5050th percentile (median) latency: half of the requests were faster than this, half slower. The "typical" request.
p9999th percentile latency: 99% of the requests were faster than this, and the slowest 1% took longer. It shows the "tail", the worst cases users actually hit.
Tail latencyThe slow end of the latency distribution (p99 and above).
wrkThe HTTP load generator used by benchmarks/run.sh. It keeps a fixed number of connections open (256 here) and sends requests as fast as the server answers.
Warm-upA short unmeasured run before each measurement, so caches and connection pools are filled before the numbers count.
UpstreamThe backend server a proxy forwards requests to. In the benchmark it's an nginx returning a 20-byte body.
Keep-aliveReusing one TCP connection for many HTTP requests instead of opening a new one each time.
RSSResident Set Size: memory pages of a process that are currently in RAM, including shared ones (program code, libraries). Summing RSS over several processes counts shared pages several times.
PSSProportional Set Size: like RSS, but each shared page is divided among the processes that share it. The fair way to compare one process (Ferrix) with a group of processes (nginx master + workers).
MB / MiBMegabyte (10โถ bytes) / mebibyte (2ยฒโฐ = 1,048,576 bytes). docker stats reports MiB.
SyscallSystem call: a request from a program to the operating system kernel (read a socket, open a file, read the clock). Each one has a fixed cost, so fast servers try to make fewer.
TSC / HPETHardware clocks. With a usable TSC (Time Stamp Counter), reading the time is almost free; machines that fall back to HPET (High Precision Event Timer) pay a slow syscall per clock read, which is why Ferrix caches the time.
perfLinux profiler used to find where CPU time goes.

Server internals

TermMeaning
WorkerA thread (Ferrix) or process (nginx) that accepts and serves connections. Both use one per CPU core in the benchmark.
Thread-per-coreEach worker thread has its own event loop and its own listening socket, so a request never moves between threads.
CPU affinityPinning a thread to specific CPU cores so the scheduler doesn't move it around (worker_cpu_affinity).
File descriptor / RLIMIT_NOFILEEvery open socket or file is a numbered descriptor; RLIMIT_NOFILE is the per-process cap on how many can be open (worker_rlimit_nofile, ulimit -n).
Listen backlogKernel queue of connections that finished the TCP handshake but weren't accepted by the server yet.
SO_REUSEPORTSocket option that lets each worker bind its own socket to the same port; the kernel spreads new connections across them.
sendfile / zero-copyLinux syscall that sends a file to a socket directly from the kernel's page cache, without copying it through the program's memory.
TCP_CORK / tcp_nopushSocket option that holds back partially filled packets, so a response header and the start of a file go out together in full packets.
TCP_NODELAYSocket option that sends small writes immediately instead of waiting to combine them (disables Nagle's algorithm).
Page cacheThe kernel's in-memory copy of recently read files.
Allocator / mimallocThe library that hands out and frees memory inside the process. Ferrix uses Microsoft's mimalloc.
LTOLink-Time Optimization: the compiler optimizes across the whole program when building the final binary.
Future (Rust)The state machine behind an async function. Its size is memory held per connection while it waits.
Tokio / hyper / axumRust's async runtime, HTTP library and web framework that Ferrix is built on.
Hot reloadApplying a new configuration without restarting the process or dropping connections.

HTTP, TLS and features

TermMeaning
Reverse proxyA server that receives client requests and forwards them to one or more upstreams.
Load balancingSpreading requests across several upstream servers (round robin, least connections, hashing...).
Consistent hashing / ketamaLoad balancing where the same key (client IP, URL...) always goes to the same server, and adding or removing a server only moves that server's keys.
Virtual host (vhost)One site served by Ferrix, selected by the Host header and port.
TLS / HTTPSEncryption for HTTP connections.
SNIServer Name Indication: the hostname a client sends during the TLS handshake, used to pick the right certificate when several sites share one port.
ACME / Let's EncryptThe protocol (and the free certificate authority) for issuing TLS certificates automatically.
HTTP-01ACME challenge where the CA checks a token at http://<domain>/.well-known/acme-challenge/... to prove you control the domain.
HTTP/2, HTTP/3, QUICNewer HTTP versions: HTTP/2 multiplexes requests over one TCP connection; HTTP/3 does the same over QUIC (UDP).
WebSocketA long-lived, two-way connection that starts as an HTTP request and is then "upgraded".
Stream (TCP/UDP) proxyForwarding raw TCP or UDP traffic (databases, DNS, MQTT) without understanding HTTP, like nginx's stream {} block.
ETag / 304 Not ModifiedA fingerprint of a file; when the browser already has that version, the server answers 304 with no body.
Range / 206 Partial ContentRequesting only part of a file (resumable downloads, video seeking).
gzip / brotli (br) / zstdCompression formats negotiated through the Accept-Encoding header.
CORSCross-Origin Resource Sharing: headers that tell browsers which other sites may call an API.
Rate limiting / token bucketCapping how many requests a client may make per second; the token bucket allows short bursts.
CIDR / ACLIP ranges written like 10.0.0.0/8 / access control lists that allow or deny them.
SSEServer-Sent Events: a one-way stream from server to browser, used by the dashboard's live request view.
PrometheusMonitoring system; Ferrix exposes metrics in its text format at /metrics.
Control plane / admin APIThe REST API (port 8081 by default) used to read and change Ferrix's configuration at runtime.