Rookin development ยท licence to be decided

// rook web server

HTTPS by default. One file to configure.

Rook is an open-source web server and reverse proxy written in Rust. It serves over TLS 1.3 from the first start, can get its own certificates from Let's Encrypt, and checks every request before your app sees it.

Made for people who run their own servers, on Linux and FreeBSD.

rookd.toml
[server]
listen = "0.0.0.0:80"
https_listen = "0.0.0.0:443"

[[sites]]
hosts = ["example.com"]
tls_cert = "/usr/local/etc/rook/example.com-cert.pem"
tls_key = "/usr/local/etc/rook/example.com-key.pem"

[[sites.routes]]
prefix = "/"
upstreams = ["http://127.0.0.1:9000"]
websocket = true
$ rookd --config rookd.toml --check
Configuration is valid: rookd.toml

// facts, not promises

  • TLS 1.3by default. 1.2 stays off unless you enable it
  • 0lines of unsafe Rust, enforced by the compiler
  • 1 fileone plain TOML config, unknown options refused
  • 4,096connections by default, 256 per address

// how a request moves

Checked before it reaches your app.

Each request passes TLS, a strict parser, your limits and routing, in that order. Anything that fails a check leaves at that point with a plain status code and no internal detail.

A request passes TLS, the parser, the limits and routing before it reaches your app. Malformed requests leave at the parser with a 400; requests over the rate limit leave at the limits with a 429. plain HTTP127.0.0.1:9000 400/public/..%3B/admin 429over 100 requests/s TLS 1.3handshakeParserstrictLimitsper addressRoutehost + prefix client yourapp
  • 400Malformed or ambiguous request, encoded slash or ;, path traversal, underscore in a header name
  • 404Hostname that isn't in your config. It's never sent upstream.
  • 421TLS hostname and Host header don't match
  • 413Body over 1 MiB (max_request_body_bytes)
  • 429Over 100 requests a second, with bursts to 200
  • 403WebSocket from an Origin not in the route's origins list
  • 502Upstream connection failed or sent an invalid response. No upstream details are shown.
  • 503 / 504No healthy upstream, or the upstream didn't answer in time

// limits

One address can't take every slot.

Every limit has a default from the first start. A connection over the limit is turned away before the TLS handshake starts. Rook tracks at most 10,000 client addresses, so a flood of new ones can't make its tables grow.

  • max_connections = 4096
  • max_connections_per_ip = 256
  • max_tracked_clients = 10000
  • requests_per_second = 100
  • request_burst = 200
  • max_header_bytes = 32768
  • header_read_timeout = 10
  • request_timeout = 30
A grid of connection slots. One address fills its share and then stops, so the remaining slots stay free for other visitors.

Each square is 64 connections, 4,096 in all.  one address stops at 256  its next connection is refused before TLS  free for everyone else

// certificates

Certificates that look after themselves.

Rook only asks for certificates for the hostnames in your config. Each new certificate is checked, then swapped in without a restart. Bring your own instead and Rook reloads it within two seconds of the file changing.

A 90-day certificate timeline. Rook renews it 30 days before expiry. If a renewal fails, it keeps serving the last valid certificate and tries again with backoff. issuedrenew_before_days = 30expires renewal fails? Rook keeps serving the last valid certificate and retries with backoff, each attempt with a five-minute deadline

// why rook

Three things Rook does differently.

  • 01

    Strict parsing first

    Every request is parsed and normalised before anything reaches your app. Ambiguous requests, encoded slashes, encoded semicolons and traversal tricks are refused with a 400, so your app only sees paths it will read the same way Rook did.

  • 02

    Limits with a fixed size

    Connections, handshakes and request rates are counted in fixed-size tables, set from the first start. A flood of new addresses fills the table and stops. It can't make Rook's memory grow.

  • 03

    No unsafe code

    Rook's own code is Rust with unsafe_code = "forbid" set for the whole crate, so the compiler refuses to build any unsafe block. It's a rule the build enforces, not a promise.

Coming from Caddy? The migration guide maps each block to Rook's config.

// status

Where Rook is today.

Rook isn't released yet. Here's exactly what works, what's next and what isn't planned for the first release.

Works today

  • TLS 1.3 by default, TLS 1.2 opt-in
  • HTTP/1.1 and HTTP/2
  • Let's Encrypt certificates (HTTP-01), or your own
  • Reverse proxy with round-robin load balancing
  • WebSockets over HTTP/1.1
  • Per-address limits and Cloudflare support
  • rookd --check validates everything without starting

Next

  • HTTPS to upstreams. Today Rook speaks plain HTTP to your app.
  • Config reload without restart
  • Upstream health checks
  • Strike and ban rules
  • Testing on FreeBSD 15.1 and against Let's Encrypt production
  • Speed figures, after a reproducible comparison with Caddy

Not in the first release

  • HTTP/3, h2c and WebSockets over HTTP/2
  • Wildcard certificates and DNS-01
  • A web application firewall

// check it yourself

Don't take the page's word for it.

Every option this site names exists in the code, and every example config passes rookd --check. The source opens with the first release, so you can build it and check it for yourself.

Read the docs
terminal
$ cargo build --release
$ rookd --config rookd.toml --check
Configuration is valid: rookd.toml
$ grep unsafe_code Cargo.toml
unsafe_code = "forbid"