The smallest useful reverse proxy.
torana terminates TLS (HTTP/1.1 and HTTP/2), balances load, drains connections gracefully, and streams traffic to your upstreams, all from one static Rust binary with no runtime and no OpenSSL.
torana · तोरण : the ceremonial gateway arch. A small structure everything passes through.
Faster than servers ten times its size.
Same Docker network, same backend, same load generator. The harness ships in the repo. Run bench/bench.sh and get your own numbers in two minutes.
Throughput (req/s, higher is better)
Idle memory (MiB, lower is better)
HTTP/1.1 plain forwarding, 64 connections, 15 s, fortio inside one Docker network on a MacBook; nginx runs its stock config, and numbers drift a few percent between runs. Treat them as a relative comparison, then reproduce them with ./bench/bench.sh.
What it does, and what it doesn't, yet.
Every config field is either enforced or warns loudly at startup. No silent ignoring, no feature claims the code can't back.
Shipping today
- ✓HTTP/1.1 and HTTP/2 (client-facing), fully streamed bodies, no buffering
- ✓Host- and path-based routing across multiple routes, first match wins
- ✓TLS termination with rustls; pure Rust, no OpenSSL
- ✓Upstream keep-alive pooling, TCP_NODELAY on both legs
- ✓Weighted round-robin load balancing
- ✓Active health checks: unhealthy upstreams are skipped, not blackholed
- ✓Retries on connect failure for GET/HEAD/OPTIONS with no body
- ✓Sandboxed WASM plugins, opt-in: fuel-limited, no host access at all
- ✓mTLS: mandatory client certs, verified fingerprint forwarded, never spoofable
- ✓Automatic HTTPS via ACME, opt-in: RFC 8555, Let's Encrypt or a private CA
- ✓Header rewriting and traffic mirroring on GET/HEAD/OPTIONS
- ✓Zero-downtime reload on SIGHUP, validated before swap
- ✓Graceful shutdown: in-flight requests finish before exit
- ✓Prometheus
/metrics,/healthz, structured JSON logs - ✓Embeddable: the engine is a separate crate usable inside your own server
On the roadmap
- ✕HTTP/3
- ✕HTTP/2 or TLS to upstreams (the proxy-to-upstream leg is always plain HTTP/1.1)
- ✕Circuit breaking (retries and health checks exist; a faster-ejecting breaker doesn't yet)
- ✕mTLS combined with ACME on the same listener
Built for the places big proxies don't fit.
Kubernetes sidecars
TLS termination and RED metrics next to your app container for 5.5 MiB of memory, not a service mesh's eighty. Probes wire straight into /healthz.
Routers, Pis, and edge boxes
A real reverse proxy on hardware where RAM is measured in megabytes and every binary ships over a slow link.
Scale-to-zero platforms
Millisecond startup means the proxy is never the reason your function's cold start got slower.
Weighted rollouts
Send 5% of traffic to the new build with two lines of TOML and a SIGHUP, no orchestration required.
docker-compose front doors
One 5.5 MB scratch image routes your whole dev stack. Smaller than most base images' package index.
Internet-facing services
HTTP/2 and automatic ACME certificates on a binary a tenth the size of a full web server.
Instrumenting legacy services
Put Prometheus request metrics in front of the service nobody dares recompile.
Internal service-to-service auth
mTLS makes client certificates mandatory and forwards a verified, unspoofable identity to your app.
Integration test fixtures
Spin up a real proxy per test on a random port. torana's own suite runs 62 tests in about a second.
Embedded products
Ship a proxy inside kiosks, appliances, and firmware images where every megabyte reaches a customer.
Running in under a minute.
Build from source with stable Rust 1.75+, use the Docker recipe, or grab a prebuilt binary from releases. Full walkthroughs live in the docs.
git clone https://github.com/abhinavcdev/torana
cd torana && cargo build --release
./target/release/torana --config torana.toml docker build -t torana . docker run -p 8080:8080 \ -v $PWD/torana.toml:/etc/torana/torana.toml torana
[[listener]] addr = "0.0.0.0:443" protocol = "https" [listener.acme] # h2 is automatic, nothing to set domains = ["example.com"] contact_emails = ["admin@example.com"] cache_dir = "./acme-cache" [[route]] name = "default" upstream = [{ addr = "http://127.0.0.1:3000" }]
# edit torana.toml, then: kill -HUP $(pgrep torana) # invalid config? torana keeps the old one
Next: the full configuration reference, Kubernetes and compose recipes, and troubleshooting.