ferrum-proxy is a Rust HTTP reverse proxy. It sits in front of your backend services, matches a request to a route, picks a healthy backend, forwards the request, and sends the response back to the client.
- path-based routing
- per-route balancing (
round_robinorfirst_healthy) - active health checks
- passive failure tracking from real traffic
- request and response streaming
- graceful shutdown and connection draining
- hardened forwarding and hop-by-hop header handling
- request and response size limits
- upstream connect and read timeouts
- bounded retries for safe and idempotent requests
- temporary backend ejection for flapping upstreams
- Prometheus-style metrics export
This project works and is useful for local testing, learning, and small internal environments.
It is not production-ready yet. The remaining gaps are mainly around broader operational hardening, deployment guidance, and more real-world validation.
src/
config/ load and validate config
server/ TCP listener and HTTP server wiring
http/ request handling and proxy flow
routing/ route matching
balancing/ backend selection
health/ active and passive health state
upstream/ outbound HTTP client
telemetry/ in-memory metrics and health transition logs
The proxy reads config.yaml.
How it works:
- requests starting with
/apigo to the/apibackend pool - requests starting with
/staticgo to the/staticbackend pool - routes can override balancing strategy, retryable statuses, passive failure statuses, health endpoints, and connect/read/body timeouts
- the health checker probes each backend on the route override or the global
/healthendpoint - only healthy backends stay in the load-balancing pool
- client headers and bodies are timed out independently from upstream reads
- request and response bodies are rejected once they exceed configured byte limits
- safe and idempotent requests can be retried within a bounded total timeout
- repeated backend failures trigger temporary ejection before active checks recover them
- slow client uploads, slow upstream response bodies, and partial client disconnects are surfaced through proxy error metrics
- debug endpoints can be hidden or protected with a bearer token
https://upstream backends are rejected for now; terminate TLS in a trusted front proxy and forward plain HTTP toferrum-proxy
cargo runThe proxy starts on the configured host and port.
On Unix-like systems, SIGHUP triggers a graceful controlled restart workflow: the proxy drains connections and exits so a supervisor can start it again with fresh config.
This repo includes a tiny Python backend for local testing:
Run these in separate terminals:
python3 scripts/test_backend.py 3001
python3 scripts/test_backend.py 3002
python3 scripts/test_backend.py 4000Leave 3003 down if you want to test unhealthy backend behavior.
Then start the proxy:
cargo runCheck the proxy:
curl -i http://127.0.0.1:8080/health
curl -i http://127.0.0.1:8080/health/backends
curl -i http://127.0.0.1:8080/metricsSend traffic through it:
curl -i http://127.0.0.1:8080/api/users
curl -i http://127.0.0.1:8080/api/users
curl -i http://127.0.0.1:8080/static/logo.pngThe test backend responds with its port number, so it is easy to see which backend handled the request.
-
GET /Basic status message. -
GET /healthHealth of the proxy process itself. -
GET /health/backendsCurrent backend health state. Can be disabled or protected with a bearer token. -
GET /metricsPrometheus text exposition with request, latency, backend failure, backend health, and error counters. Can be disabled or protected with a bearer token.
Run the full test suite:
cargo testThe repo has:
- unit tests for routing, balancing, health, config, upstream, and telemetry
- integration tests for request-path behavior
- black-box tests that start the real server and hit it over HTTP
This repo benchmarks the live proxy with wrk, not a function-level microbenchmark.
Install wrk, then run:
cargo run --release --bin benchmark_runner --The runner builds the release binaries, starts dedicated local benchmark backends, runs warmup and measured wrk passes, and stores results under benchmark-results/<timestamp>/.
The default benchmark suite covers:
healthy_getsteady-state GET traffic through healthy backendslarge_responselarge streamed upstream responsesretry_getone failing backend plus one healthy backend to measure retry costupload_postPOSTrequests with a 64 KiB request body
Useful commands:
cargo run --release --bin benchmark_runner -- --scenarios healthy_get,retry_get
cargo run --release --bin benchmark_runner -- --warmup 10s --duration 30s
cargo run --release --bin benchmark_runner -- --skip-buildThe benchmark output is the native wrk report. The healthy_get measured pass from the May 24, 2026 run in benchmark-results/20260524-194445/ looked like:
Running 15s test @ http://127.0.0.1:55845/api/users
4 threads and 128 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 4.89ms 7.48ms 117.35ms 95.86%
Req/Sec 8.45k 2.16k 11.10k 73.50%
Latency Distribution
50% 3.43ms
75% 4.43ms
90% 6.38ms
99% 41.07ms
504964 requests in 15.01s, 581.26MB read
Requests/sec: 33642.70
Transfer/sec: 38.73MB
Latest measured snapshot from May 24, 2026:
healthy_get:33642.70requests/sec,p99 41.07mslarge_response:10430.62requests/sec,2.55GB/secretry_get:21891.36requests/sec,p99 10.80msupload_post:18625.54requests/sec,p99 9.68ms
Focus on:
Requests/secfor throughputLatency Distribution, especially90%and99%Transfer/secfor body-heavy scenarios
For more detail, see docs/benchmarking.md.