Most programs that speak HTTP don't want a web server. They want a door: a place where requests come in and answers go out. A sensor reports its readings. A function wakes up, answers one call and goes back to sleep. A desktop app shows a dashboard in the browser. An integration engine takes webhooks all day.
In Rust, that door usually comes with a house attached: an async runtime, a few dozen crates, and a megabyte or more of code you didn't write and can't easily audit.
simoom is just the door. You add one crate, write a function that answers a request, and
call start(). Underneath, it serves HTTP/1.1, HTTP/2 and HTTP/3, does its own TLS 1.3, and
runs one event loop per CPU core on epoll, io_uring, IOCP or kqueue. There is nothing in it
but the Rust standard library and code in this repository.
use ;
use Arc;
That server starts in under a millisecond, idles at about 1 MB of memory, and adds about 215 KB to your binary if HTTP/1.1 is all you need.
Contents
- Why simoom exists
- What you get, in numbers
- Where simoom fits
- When to use something else
- Quick start
- Examples
- Pay only for what you use
- The benchmarks, and how they were run
- Where simoom is behind
- How it is tested
- Status and limits
- FAQ
- Documentation
- Contributing, security and licence
Why simoom exists
simoom was built for Cameleer, which runs Apache Camel integration routes in Rust using only the standard library. Cameleer's first HTTP server gave every connection its own thread. That is simple and it works, but threads are heavy: with 1,000 idle connections open it used 7.7 MB, and with the server on 4 CPU cores it topped out at about 173,000 requests per second.
Cameleer needed something better, with rules that ruled out the usual answers:
- No external crates. Cameleer uses nothing outside std, so tokio, hyper, rustls and OpenSSL were all off the table.
- Small enough for edge devices and serverless functions, where every kilobyte and every millisecond of start-up counts.
- Every protocol, HTTP/3 included, in the first release.
- Faster than what it replaces, and never more than 5% worse on start-up time or memory, measured on the same machine.
So simoom was written from the ground up: the HTTP parsers, HTTP/2, QUIC and HTTP/3, the TLS 1.3 stack and its cryptography, the compression codecs and the event loops for four operating systems. On the same machine as Cameleer's old server, simoom serves 1.7 to 1.8 times as many requests, with a p99 latency of 0.32 to 0.44 ms instead of 0.87 to 0.91 ms, and holds 1,000 idle connections in 2.7 MB instead of 7.7 MB.
The name. A simoom is a hot, fast wind that crosses the desert. It belongs to the same caravan as Cameleer and Cameleering.
The rule. simoom is a Cameleering project. Cameleering means making software carry the same load on less, and proving it against the original. No claim without a number: every figure in this README comes from results committed in this repository, and the scenarios where simoom loses are published next to the ones where it wins.
What you get, in numbers
Every comparison below was measured on the same machine as the server it is compared with. The method is under The benchmarks.
| simoom | compared with | |
|---|---|---|
| Requests per second with p99 under 1 ms, plain HTTP, 256 connections | 172,638 | hyper 144,443 · may-minihttp 150,673 |
| The same over TLS 1.3 | 160,441 | h2o 97,219 |
| HTTP/3 requests per second with p99 under 1 ms | 106,947 | h2o 94,507 |
| New TLS 1.3 connections per second, full / resumed | 10,083 / 11,213 | h2o 7,711 / 7,758 |
| CPU time per request at 50,000 requests per second | 25.05 µs | hyper 28.10 µs |
| Start-up to the first answer, plain / TLS | 0.73 ms / 0.87 ms | Cameleer's old server 0.77 / 1.37 ms |
| Memory per idle connection, 100,000 open | 520–533 bytes | nginx 532 bytes |
| Code added to your binary, HTTP/1.1 only | 215 KB | budget 250 KB |
| Code added to your binary, everything including HTTP/3 | 935 KB | budget 1 MB |
Not every row is a win. simoom falls behind under heavy overload, on a degraded network path and in a few other cases, all listed under Where simoom is behind.
Where simoom fits
On a device at the edge. A gateway on a factory floor has an ARM chip, 256 MB of RAM and a
firmware image that has to stay small. It needs to accept readings over HTTP and offer a small
status page. With only the http1 feature, simoom adds about 215 KB to the binary and holds
idle connections in about half a kilobyte each. Even with TLS, the build needs no C compiler, no
cmake and no OpenSSL headers for the target. (The ARM code paths compile but have not been
benchmarked yet.)
In a serverless function. A function that scales to zero pays for its start-up on every cold request. simoom gives the first answer 0.73 ms after the process starts, about 0.87 ms with TLS, and idles at about 1 MB, so the time and memory go to your code instead of the HTTP layer.
Inside a CLI or desktop app. A command-line tool needs a local callback for an OAuth login. A desktop app wants to show a live dashboard in the browser and push updates over WebSocket or server-sent events. simoom runs on Windows, Linux and macOS, starts in a millisecond and pulls no async runtime into a program that never needed one.
In an integration engine. Webhooks, REST and MCP endpoints, streaming responses, and calls that
block on a database or a slow partner system. simoom answers fast work on the loop thread, sends
slow work to a bounded pool that says 503 instead of piling up, and lets a host with its own
threads answer later from any of them. This is how Cameleer
uses it.
Where every line of code has to be accounted for. A bank, a medical device maker or a defence
supplier has to review every third-party dependency and track it for vulnerabilities. simoom has
none: no crates.io dependencies at all, not even for tests. Every line, from the TLS handshake to
the QUIC congestion control, is in this repository under one licence. unsafe code lives in one
crate, simoom-sys, where each block explains why it is sound.
For a service that wants HTTP/3. Mobile clients on flaky networks benefit from HTTP/3 over
QUIC. With simoom, HTTP/3 is a cargo feature on the same server: TCP and QUIC listen on the same
port number, and browsers are told about HTTP/3 with alt-svc. It passes 54 of 58 supported
pairs in the QUIC Interop Runner, and the four failures are features it does not offer (0-RTT and
ECN).
When to use something else
simoom is new, small and focused. Pick the alternative when it fits better:
| If you need | Use | Why |
|---|---|---|
| A web server that runs from a config file, serves static files or proxies to other servers | nginx, Caddy or h2o | simoom is a library you build into a program; it has no config file, no static-file server and no reverse proxy. |
| A large ecosystem of middleware, extractors and integrations, and many people who already know the API | axum or actix-web | simoom's API is small and young. It has a router and optional helpers, not an ecosystem. |
| A low-level HTTP library inside the tokio world, or one with years of production use | hyper | hyper is mature and battle-tested. simoom is at version 0.1.0. |
| The very simplest blocking server for a tool or a test | tiny_http | It is tiny and easy. simoom gives you more protocols and more throughput, at more API surface. |
| A general-purpose QUIC transport, raw QUIC streams, or 0-RTT | quinn or s2n-quic | simoom's QUIC exists to carry HTTP/3. It refuses 0-RTT by design and has no ECN yet. |
| An independent security audit of the TLS stack | rustls with ring or aws-lc | simoom's TLS and crypto have been tested hard but not independently audited yet. |
Quick start
simoom needs Rust 1.89 or newer.
or in Cargo.toml:
[]
= "0.1"
The client is a separate crate: cargo add simoom-client. The API documentation is on
docs.rs.
To run the bundled hello-world server from a checkout:
A service is a plain function that takes a request and a reply and returns an Outcome. It
runs on the loop thread, so it must not block: there is no async and no runtime to set up.
Work that blocks goes to the offload pool or to your own threads, as the examples show.
Examples
These examples compile against the current API with --features full.
Routing, JSON, request bodies and limits
use ;
use Duration;
let router = new
.get
.get
.route;
let server = builder
.listen
.limits
.service
.start
.expect;
Slow work: the offload pool, or your own threads
Work that takes a while, such as a database query or a call to another service, goes to the
bounded offload pool. When the pool is full, the client gets a fast 503 with Retry-After
instead of waiting in an endless queue:
use ;
let router = new.get;
A host that already has its own threads can defer the reply and answer from any of them. This is how Cameleer embeds simoom:
use ;
let server = builder
.listen
.service
.start
.unwrap;
TLS, HTTP/2 and HTTP/3 on one port
Build with --features full, or with tls and http3. Over TCP, the client and server agree on
HTTP/2 or HTTP/1.1 during the TLS handshake. The QUIC listener serves HTTP/3 on the same port
number:
use ;
use ;
// a self-signed certificate for local testing; load your own PEM files in production
let cert = self_signed;
let key = from_pem.expect;
let tls = new;
let server = builder
.listen_tls
.tls
.listen_quic // HTTP/3, announced with alt-svc
.quic_tls
.service
.start
.expect;
Live updates: server-sent events and WebSocket
use ;
use Duration;
;
let router = new
.get
.get;
Threads, io_uring and a graceful stop
use ;
use Duration;
let server = builder
.listen
.threads // 0 = one loop per core (the default)
.backend // Linux 6.1+, falls back to epoll; the default `Auto` is epoll on Linux
.service
.start
.unwrap;
// ...
server.stop; // tells clients to finish, then waits up to 5 s
server.join;
The HTTP client
use ;
use Duration;
let client = builder
.connect_timeout
.follow_redirects
.build;
let resp = client.get.send?; // HTTP/2 or HTTP/1.1, agreed in the handshake
println!;
let resp = client
.post
.header
.body
.version // h2c with prior knowledge
.timeout
.send?;
The client speaks HTTP/1.1, HTTP/2 and HTTP/3, pools connections per origin, moves to HTTP/3 when a server offers it, and handles proxies, redirects, decompression, cookies and WebSocket. Certificate verification is always on.
The smallest build, for a device or a function
Turn off everything but HTTP/1.1:
= { = "0.1", = false, = ["http1"] }
Pay only for what you use
Every protocol is a cargo feature, and a build script fails when a size budget is exceeded.
| feature | adds |
|---|---|
http1 (default) |
HTTP/1.1 |
http2 (default) |
HTTP/2: h2c, Upgrade: h2c, h2 over TLS |
websocket (default), alias ws |
WebSocket on HTTP/1.1 |
io-uring (default) |
the Linux io_uring back end (Backend::IoUring or SIMOOM_BACKEND=io_uring); without it, epoll |
watchdog, guard, idle-trim (default) |
moves slow handlers off the loop; guards against overload and slow clients; returns memory after bursts |
tls |
TLS 1.3 listeners: X25519, P-256, RSA, AES-GCM |
tls-extra |
P-384 and Ed25519 certificates, ChaCha20-Poly1305 |
http3 |
HTTP/3 over QUIC (needs tls) |
full |
HTTP/1.1, HTTP/2, HTTP/3, TLS with tls-extra, WebSocket, io_uring, the default safety nets and the TLS client; not compression, websocket-h2/h3, ws-deflate, h2-keepalive or the helpers below |
compression, brotli, zstd |
response compression and request decoding |
websocket-h2, websocket-h3, ws-deflate |
WebSocket over HTTP/2 and HTTP/3; permessage-deflate |
h2-keepalive |
HTTP/2 PING keepalive |
multipart, cookies, conditional, cors, auth |
helpers for forms, cookies, caching, CORS and Basic, Digest and Bearer auth |
What each build adds to a binary, measured as the bytes added to
examples/hello.rs over an empty fn main, stripped, on
x86-64 Linux with the dist profile (scripts/size.sh):
| build | size | budget |
|---|---|---|
HTTP/1.1 only (--no-default-features --features http1) |
214,568 B | 256,000 B |
| HTTP/1.1, HTTP/2, TLS, io_uring | 583,680 B | 614,400 B |
| everything: HTTP/1.1, HTTP/2, HTTP/3, TLS, WebSocket, io_uring | 935,064 B | 1,048,576 B |
The HTTP/1.1 and full rows are from 2026-11-11 and the TLS row from 2026-11-12 (BUGS.md U9). As a whole executable, the HTTP/1.1-only server is 536 KB stripped, against may-minihttp's 515 KB; about 181 KB of either is the standard library's backtrace code.
What is inside
| crate | what it is |
|---|---|
simoom |
the public API: Server, Service, Reply, Responder, Router, Limits |
simoom-client |
HTTP/1.1, HTTP/2 and HTTP/3 client and WebSocket client |
simoom-rt |
one event loop per core, timers, buffer pools, wakeups, the offload pool |
simoom-sys |
epoll, io_uring, kqueue, IOCP, sockets, SIMD; the only crate with unsafe |
simoom-http1 |
strict HTTP/1.1 parser and serializer (RFC 9112) |
simoom-h2, simoom-hpack |
HTTP/2 (RFC 9113) with flood protection, HPACK (RFC 7541) |
simoom-quic |
QUIC v1 transport (RFC 9000, 9002) |
simoom-h3, simoom-qpack |
HTTP/3 (RFC 9114), QPACK (RFC 9204) |
simoom-tls |
TLS 1.3, server and client, and the QUIC interface (RFC 9001); the crate also has TLS 1.2 as its own tls12 feature, which the simoom server does not expose |
simoom-compress |
DEFLATE, zlib, gzip, Brotli and zstd, streaming and bounded |
The protocol crates are sans-io: they take bytes and time and return bytes and events, and never open sockets, start threads or read clocks. You can drive them from your own I/O loop. The cryptography is planned to move into ksar, a shared crypto family for Cameleering projects.
The benchmarks, and how they were run
- Machines: DigitalOcean
c-4droplets: 4 dedicated vCPUs, Xeon Platinum 8280, Bangalore. - Load: from two separate load machines, open loop, so a slow server can't slow the load down and hide its own latency.
- Headline figure: the highest steady rate at which 99% of requests are answered within 1 ms.
- The basic test (S1): a GET with a 13-byte answer, one request in flight per connection.
- Same machine only: droplets of the same type differed by up to 7x for every server, so only servers measured on the same droplet are compared.
- Everything is published: the raw HDR logs, the scripts and the analyses are committed.
Plain HTTP, 2026-11-12
S1, requests per second with p99 under 1 ms, median of three rounds on one droplet. The single rounds are in the analysis.
| server | 64 connections | 256 connections | 1,024 connections |
|---|---|---|---|
| simoom (epoll) | 170,648 | 172,638 | 138,644 |
| simoom (io_uring) | 152,714 | 172,331 | 148,429 |
| may-minihttp | 158,598 | 150,673 | 120,610 |
| hyper | 122,240 | 144,443 | 115,102 |
These figures replace the plain-HTTP rows of the earlier campaign below, in which simoom trailed may-minihttp at 64 and 256 connections and hyper at 1,024. The cause was a loop sharing a CPU with the network card's interrupts; the fix (BUGS.md K12) and the before-and-after runs are in the analysis.
TLS 1.3 handshakes per second in the same round, 192 connections, four runs: 8.7k full and 9.8k resumed with epoll, 8.9k–9.4k and 9.9k–10.6k with io_uring. CPU per request at 50k req/s: 25.47 µs with epoll, 25.26 µs with io_uring.
Against nginx, h2o, hyper, haproxy and others, 2026-11-11
Each row compares servers that ran on the same droplet; the analysis names the droplet for each figure.
| scenario | simoom | best other on the same droplet |
|---|---|---|
| TLS 1.3 full / resumed handshakes per second | 10,083 / 11,213 | h2o 7,711 / 7,758 (simoom +31% / +45%) |
| S1 over TLS, 256 connections | 160,441 | h2o 97,219 (simoom +65%) |
| HTTP/3 S1 | 106,947 | h2o 94,507 (simoom +13%) |
| CPU per request at 50k req/s | 25.05 µs | hyper 28.10 µs (simoom 11% less) |
| 1 KB JSON echo | 153,526 | hyper 152,822 (a tie) |
| HTTP/2 h2c, 100 streams, p99 under 5 ms | 152.5k | h2o 152.6k (a tie, at the network limit) |
| 1 MB responses, plain and TLS | 1.95 Gbit/s | nginx 1.95 Gbit/s (a tie, at the network limit) |
| peak memory during S1 at 256 connections | 2.5 MB | xitca 4.1 MB |
| memory per idle connection, 10k / 100k open | 594 / 533 B (default build 520 B at 100k) | nginx 522 / 532 B |
| a handler that blocks for 100 ms, 500 clients | 4,925 req/s, 0 errors | Cameleer's old server 4,925 |
Full tables: summary.md and compare.md.
Against the server it replaced
Cameleer's old thread-per-connection server (cameleer-http) and simoom, both returning the same
13-byte answer on the same machine (WSL2). Start-up and idle memory, 10 alternating rounds,
2026-11-11:
| build | start-up to first answer | idle memory (PSS) |
|---|---|---|
| simoom, plain | 0.73 ms | 1,018 KB |
| cameleer-http, plain | 0.77 ms | 1,017 KB |
| simoom, TLS (P-256) | 0.87 ms | 1,360 KB (after the K11 fix, 2026-11-12) |
| cameleer-http, TLS | 1.37 ms | 1,317 KB |
Under load (server on 4 CPUs, 64 connections, closed loop, 2026-11-09):
| simoom | cameleer-http | |
|---|---|---|
| throughput | 293,000–311,000 req/s | 172,000–173,000 req/s |
| p99 latency | 0.32–0.44 ms | 0.87–0.91 ms |
| server CPU per request | about 13 µs | about 23 µs |
| memory with 1,000 idle connections | 2.7 MB | 7.7 MB |
Where simoom is behind
Losses are part of the result:
- Heavy overload. At 195k req/s offered, 1.17 times what the droplet's network card can carry, simoom answers the most requests in total (160.3k against nginx's 145.5k), but nginx answers far more of them within 1 ms (52.4k against simoom's 6.4k). nginx's figure changed by 20x between droplets; this is recorded as a limit of the benchmark at that rate (BUGS.md K13).
- A degraded network path. On a droplet with a slow network path, may-minihttp kept 114.7k req/s while simoom and the others fell to 17–62k. simoom's fix for interrupt placement (K12) has not been rerun on such a droplet.
- A new connection for every request. On one droplet simoom handled 19.3k connections per second against xitca's 24.3k (−21%). On another, simoom led with 22.1k against hyper's 19.3k.
- Slow attackers. With 10,000 slowloris and 1,000 slow-read attackers, simoom's p99 for real requests was 8% above haproxy's (784 µs against 724 µs).
- Idle memory with TLS: 3.3% above Cameleer's old server (1,360 KB against 1,317 KB).
- Executable size: the HTTP/1.1-only executable is 4% larger than may-minihttp's.
The raw results are in bench/results/. To rerun the benchmarks, use
bench/cloud/ and scripts/size.sh. If you measure something
differently and get a different number, please open an issue with your method.
How it is tested
simoom follows RFC 9110, 9112, 9113, 9114 and 9204 strictly: any request whose framing is
ambiguous is refused, every limit has a default, and certificate verification can't be turned off.
Every crate except simoom-sys is #![forbid(unsafe_code)]. Versions, commands and details are
in docs/SECURITY.md.
| check | result |
|---|---|
| h2spec 2.6.0 | 146/146, in 30 consecutive runs |
| QUIC Interop Runner, server role, against the ngtcp2, quiche and quic-go clients | 54 of 58 supported pairs; the 4 failures are 0-RTT (refused by design) and ECN (not implemented) |
| HTTP Garden, as an origin next to nginx, Apache httpd, h2o, hyper, gunicorn and Go | 0 desyncs, 0 dangerous discrepancies |
| Request-smuggling test behind nginx 1.24 and haproxy 2.8 | 165 streams, 0 desyncs; accepted 0 requests that both refused |
| testssl.sh 3.2.1 | no vulnerabilities found |
TLS interop: OpenSSL s_client; curl with OpenSSL, Python ssl and gnutls-cli; curl with Schannel |
28/28, 31/31, 3/3 |
| nghttp and h2load | 14/14 |
| Fuzzing | std-only mutation fuzzing of every parser for 2 h 40 min, and coverage-guided fuzzing of HTTP/1.1 |
Test suite, cargo test --workspace |
777 passed on Windows, 791 on Linux (WSL2), zero warnings |
Status and limits
- Version 0.1.0. The API can still change.
- No independent security audit yet. The testing above was done by the project itself.
- QUIC: no 0-RTT (refused by design), no ECN, no migration to a preferred address.
- Platforms: the test suite runs on Windows and Linux. macOS (kqueue) and the ARM SIMD and crypto paths are compile-checked only.
- Benchmarks: every cloud figure comes from 4-vCPU droplets with a single-queue network card. Larger machines have not been measured.
- A library, not a product: no static-file server, reverse proxy or configuration file.
The current state and open items are in docs/STATUS.md and docs/BUGS.md.
FAQ
Is there a Rust HTTP server without tokio?
Yes. simoom has no async runtime at all. A service is a plain function that runs on the loop
thread; slow work goes to a bounded offload pool (simoom::blocking) or to your own threads
(Reply::defer). Streaming, server-sent events and WebSocket all work without async.
Is there a Rust web server with zero dependencies?
Yes. simoom's workspace has no crates.io dependencies, not even dev-dependencies. Calls into the
operating system go through extern declarations in simoom-sys, and SIMD goes through
core::arch. The only exceptions are the competitor programs under bench/competitors/, which are
separate projects that exist only to be benchmarked against.
simoom is std-only, not no_std: it uses the Rust standard library, so it needs an operating
system (Linux, Windows or macOS). It does not run on bare-metal microcontrollers.
Can I serve HTTP/3 from Rust without quinn, rustls or OpenSSL?
Yes. simoom implements QUIC v1, HTTP/3, QPACK and TLS 1.3 itself, and HTTP/3 is a cargo feature of the same server that serves HTTP/1.1 and HTTP/2. It passes 54 of 58 supported pairs in the QUIC Interop Runner against the ngtcp2, quiche and quic-go clients, and on the same droplet it served 106,947 HTTP/3 requests per second with p99 under 1 ms, against h2o's 94,507.
What is the smallest Rust HTTP server for AWS Lambda or other serverless platforms?
An HTTP/1.1-only simoom build adds about 215 KB to a binary, gives the first answer 0.73 ms after
the process starts and idles at about 1 MB. Turn off the default features and keep http1.
How do I embed an HTTP or WebSocket server in a Rust app?
Add simoom, build a Server with Server::builder(), give it a service or a Router, and call
start(). It runs on its own threads and returns a handle you can stop() and join(). See
Quick start and the WebSocket example.
How does simoom compare with hyper and axum?
On the same 4-vCPU droplet, simoom served 172,638 plain requests per second with p99 under 1 ms at 256 connections, against hyper's 144,443, and used 11% less CPU per request at a fixed 50k req/s. hyper (which axum is built on) is far more mature and has a large ecosystem; simoom has no dependencies and a much smaller footprint. Choose by what matters more to you.
Can it run on a Raspberry Pi or another ARM board?
It is built for small devices, and the ARM SIMD and crypto paths compile. They are compile-checked only so far, and the benchmarks ran on x86-64. Reports from ARM hardware are welcome.
Can I use it as a reverse proxy or a static-file server?
No. simoom is an engine you build into a program. For those jobs, use nginx, Caddy or h2o.
Which platforms does it run on?
Linux (epoll and io_uring), Windows (IOCP) and macOS (kqueue). The test suite runs on Windows and Linux; macOS is compile-checked only.
Is it safe to put on the internet?
It was built for it: strict parsing, a limit on everything, protection against HTTP/2 floods,
slowloris and reconnect storms, and a panic in a handler becomes a 500 instead of a crash. It
has not had an independent security audit yet; see docs/SECURITY.md.
Documentation
| docs/STATUS.md | the current state and latest measurements |
| docs/design/plan.md | design, scope and size budgets |
| docs/design/research.md | the research behind the design |
| docs/SECURITY.md | security model, limits, test results, reporting a vulnerability |
| docs/BUGS.md | bugs found and fixed, open items |
| docs/parity/ | parity with Cameleer's client, server and TLS |
| bench/results/ | benchmark campaigns: raw data and analyses |
| llms.txt | a summary for AI assistants |
| API docs | docs.rs/simoom, or cargo doc -p simoom --features full --open |
Contributing, security and licence
- Contributing: issues and pull requests are welcome; see CONTRIBUTING.md.
- Security: report vulnerabilities privately through GitHub security advisories. Details are in docs/SECURITY.md.
- Licence: Apache License 2.0.
simoom is a Cameleering project, alongside Cameleer, which runs Apache Camel routes in Rust. simoom is independent of the Apache Software Foundation. Apache and Apache Camel are trademarks of the ASF.