A single-binary web server and reverse proxy, written in Rust. Automatic HTTPS from Let's Encrypt, HTTP/3, load balancing with health checks, auth and rate limiting — configured in one TOML file, no code required. When you outgrow configuration, the same engine is a Rust library.
Website: rws8.tech
brew install bohdaq/tap/rws # macOS / Linux — or: cargo binstall rust-web-server
docker run -p 7878:7878 -v "$PWD:/srv" bohdaq/rwsPrebuilt binaries for Linux (static, any distro), macOS, and Windows are on GitHub Releases.
cd my-site && rwsServes the current directory on http://localhost:7878 — index.html, directory listings, ETags and browser caching (304 revalidation), gzip, byte ranges (video seeking, resumable downloads), and an optional SPA fallback for client-side routers. rws --help lists every option.
# rws.config.toml
port = 443
http_redirect_port = 80
[acme]
domains = ["example.com", "www.example.com"]
email = "you@example.com"rws gets certificates from Let's Encrypt on first start, serves HTTPS with HTTP/2 and HTTP/3, redirects HTTP to HTTPS, and renews in the background with zero-downtime reloads. Retries until DNS is ready, IPv4 and IPv6, private ACME CAs supported. → Automatic HTTPS
[[upstream]]
name = "app"
backends = ["10.0.0.10:8080", "10.0.0.11:8080"]
strategy = "least_connections"
[upstream.health_check]
path = "/healthz"
[[route]]
[route.match]
host = "api.example.com"
path = "/v1/*"
[route.action]
type = "proxy"
upstream = "app"
[route.middleware]
rate_limit = { max_requests = 500, window_secs = 60 }
auth = { type = "bearer", token_env = "API_TOKEN" }Load balancing (round-robin, least-connections, random, IP hash), active health checks, bearer / JWT / basic auth, rate limits, caching, IP allow/deny lists, header and URL rewrites, per-route timeouts and body limits — plus TCP, UDP, WebSocket, and gRPC proxying. Misconfigured auth fails closed. Circuit breakers, canary splits, forward auth, and service discovery are available from the library. Combine with [acme] for HTTPS in front of your apps. → Config-driven proxy · full config reference
The server above is the rust-web-server crate. Use it directly when you need routes, middleware, or an MCP server in code:
# Cargo.toml
[dependencies]
rust-web-server = "18"
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }use rust_web_server::prelude::*;
fn hello(_: &Request, _: &PathParams, _: &ConnectionInfo, _: &()) -> Response {
Response::text(STATUS_CODE_REASON_PHRASE.n200_ok, "Hello, world!")
}
#[tokio::main]
async fn main() {
let app = routes! {
App::with_state(()),
GET "/hello" => hello,
};
let (listener, pool) = Server::setup().unwrap();
tokio::join!(
Server::run_tls(listener, pool, app.clone()),
Server::run_quic(app),
Server::run_redirect(),
);
}Routing with path parameters, typed extractors, one Middleware trait for everything (auth, rate limiting, caching, tracing, the reverse proxy itself), WebSocket, SSE, an MCP server, an async ORM, background jobs, object storage, a mailer, and OAuth/OIDC/SAML login — all features, building apps.
- One binary, one file. Static site, HTTPS, reverse proxy, and gateway features in a single executable configured by one
rws.config.toml— no separate certbot, cron job, or proxy process. - No hyper, no Tower, no Axum. HTTP/1.1 parsing, routing, middleware, WebSocket, SSE, CORS, range requests, JWT, and the ACME client are implemented in this crate. The HTTP/2 server is rws's own too (checked with h2spec and fuzzing); HTTP/3 uses the widely deployed
quinn/h3crates, and TLS usesrustls— no OpenSSL. - Config and code share one engine. Everything the TOML file does is a type you can use from Rust, so moving from configuration to code doesn't mean switching tools.
- Tested against the real world. CI fuzzes every parser that sees untrusted input, checks HTTP/2 conformance with h2spec, and runs the ACME flow end to end against Let's Encrypt's test CA on every push. Report vulnerabilities privately — see SECURITY.md.
- Fast. Up to 5× faster than Caddy, and less CPU per request than nginx in every scenario of the published benchmark (results vary by hardware) — see Performance.
- SemVer. Breaking changes only in a major version — see the CHANGELOG.
Requests per second (p99 latency), on one 4-CPU GitHub Actions runner with
the load generator on the same machine: oha, 64 keep-alive connections,
20 s per scenario, no compression, access logs off; nginx with
open_file_cache.
| Scenario | rws | Caddy 2.11 | nginx |
|---|---|---|---|
| Static file, 1 KiB | 96,670 (1.7 ms) | 18,592 (13.5 ms) | 81,161 (3.1 ms) |
| Static file, 1 MiB | 9,511 (14.0 ms) | 2,398 (124 ms) | 9,767 (14.1 ms) |
| Reverse proxy, 1 KiB | 34,260 (3.9 ms) | 13,402 (14.2 ms) | 33,942 (4.5 ms) |
| HTTP/2, 1 KiB | 66,141 (2.4 ms) | 13,604 (14.2 ms) | 51,416 (4.5 ms) |
| 1 MiB over HTTPS | 1,602 (49 ms) | 1,460 (108 ms) | 1,311 (101 ms) |
| CPU per request (1 KiB / 1 MiB / proxy / HTTP/2 / 1 MiB HTTPS) | 16.9 / 210 / 44.6 / 27.4 / 924 µs | 154 / 1,195 / 191 / 216 / 1,484 µs | 25.1 / 216 / 47.1 / 37.9 / 1,640 µs |
| Peak memory | 34–66 MiB | 66–109 MiB | 23–42 MiB |
rws is 2.6–5.2× faster than Caddy (1.1× for large files over HTTPS) and has the lowest p99 of the three in
every scenario. It needs less CPU per request than nginx in every scenario:
small files −33%, large files −3%, proxying −5%, HTTP/2 −28%, HTTPS −23%,
large files over HTTPS −44%, access-logged traffic −29%, gzip a hundredth —
and serves more requests per second in all but large plain files (−3%).
That's on the AMD EPYC runners; on the fastest (Intel) runner type nginx
keeps a few percent on plain-HTTP throughput, which the opt-in
RWS_CONFIG_RUNTIME=per-core narrows. Release builds use the mimalloc
allocator: less CPU, but more memory than nginx (build without the
mimalloc feature for ~9 MiB) — see the
benchmarks page. Shared CI runners are noisy, so
compare servers within a run, not across runs. Reproduce it with
bench/run.sh or the manual
Benchmark workflow.
Everything is on rws8.tech (source: docs/):
- Quick start · Installation and feature flags · All features
- Configuration — environment variables,
rws.config.toml, CLI flags - Building apps — routing, state, extractors, middleware, templates, the ORM
- Proxy · MCP server · Deployment
- Architecture · Troubleshooting · Migrating to 18.0
- API reference: docs.rs/rust-web-server · Changes: CHANGELOG.md
rws is easy for coding assistants to get right: one routing pattern, one
Middleware trait for everything, and no hyper/Tower/Axum types to confuse
with this crate's own. Point your assistant at:
- llms.txt — an index of the docs with a one-line summary per page;
raw:
https://raw.githubusercontent.com/bohdaq/rust-web-server/main/llms.txt - llms-full.txt — every docs page in one file, for pasting into a chat or system prompt
- CLAUDE.md — architecture and conventions; Claude Code reads it automatically inside this repo
I'm building on the rust-web-server (rws) crate. Read
https://raw.githubusercontent.com/bohdaq/rust-web-server/main/llms-full.txt
then build a REST API with GET/POST /todos backed by SQLite (model-sqlite),
JWT auth on POST (auth), and per-IP rate limiting on every route.
To make your app a tool that Claude, Cursor and other MCP clients can call, see the MCP server.
git clone https://github.com/bohdaq/rust-web-server.git
cd rust-web-server
cargo build --release # target/release/rws; MSRV 1.88--no-default-features --features http2 drops HTTP/3; --features http1
builds a plain HTTP/1.1 server with no TLS and no async runtime. Contributor
guide: DEVELOPER.md.
Issues labelled good first issue are a good place to start; questions go to Discussions. See CONTRIBUTING.md for how to build, test and send a change.
MIT