The reverse proxy that
routes on what’s inside
the request.
limer.sh is an ultra-fast HTTP server and load balancer that distributes traffic by JSON fields, form fields, body regexes, headers, cookies, the path, or the client address — not just the URL. One binary, a readable Limerfile, automatic HTTPS, HTTP/2 and gRPC.
example.com {
# Same customer → same upstream, chosen by a field in the JSON body
route orders {
match method POST
match json type == order refund
reverse_proxy 10.0.1.10:8080 10.0.1.11:8080 {
lb_policy hash json customer.id
}
}
# Everything else: least-connections with active health checks
reverse_proxy 10.0.0.10:8080 10.0.0.11:8080 10.0.0.12:8080 {
lb_policy least_conn
health_uri /healthz
}
}
Balance on the request, not just the URL.
Most proxies can only see the method and path. limer.sh looks inside the request and
picks a handler or an upstream from its contents. lb_policy hash json customer.id
sends every request for a customer to the same backend with weighted rendezvous hashing — so
when an upstream leaves, only its keys get remapped.
Match with == != ~ !~ > < ^= *= exists missing, AND matchers together, OR their values, and negate any of them.
- json
- form
- body regex
- header
- cookie
- query
- path
- host
- method
- remote_ip
- tls_client
- split %
:8080 {
route events {
match path /events/*
match json event == signup purchase
to :9001 :9002 :9003
lb_policy hash json tenant
}
# 5% canary — sticky per user
route {
match split 5 cookie session
reverse_proxy canary.internal:8080
}
reverse_proxy :9001 :9002 :9003
}
A full edge, nothing to assemble.
limer.sh works like Caddy: automatic certificates, zero-downtime reloads, an admin API — plus the payload routing and balancing that Caddy lacks.
Payload routing & hashing
Route and balance on JSON, form, body, header, cookie, query, path or client IP. Rendezvous hashing keeps keys stable across upstream changes.
Automatic HTTPS
Let’s Encrypt over http-01, tls-alpn-01 or dns-01 with wildcards. Renews itself. tls internal for local dev.
HTTP/2 & gRPC
HTTP/2 both sides via h2c:///h2://, trailers intact. Unary and bidirectional streaming gRPC pass straight through.
Smart load balancing
Round robin, least-conn, random, IP/URI hash, sticky cookies — with weights, active health checks and passive ejection.
Caching & rate limits
In-memory LRU cache honouring Cache-Control, ETag and stale-while-revalidate. Token-bucket rate limits keyed by any placeholder.
Static files & compression
file_server with ranges, try_files and directory browsing. gzip, zstd and brotli, streamed and negotiated.
Zero-downtime reloads
Hot reload on SIGHUP or the admin API. A bad config is rejected and the old one keeps running. In-flight requests finish cleanly.
Dynamic upstreams
Pools from DNS A/AAAA or SRV records, re-resolved on an interval. Mutual TLS, traffic mirroring and canary splitting built in.
Ops you can see
Prometheus metrics, JSON upstream status, runtime drain and weight overrides that survive reloads, multi-process workers with SO_REUSEPORT.
An epoll data path in C — about 3–5× Caddy.
Same 4-vCPU VM, same two backends, same round_robin policy and an enlarged keep-alive pool, driven by wrk. HTTP/1.1 reverse-proxy throughput:
| Scenario | Caddy v2.10 | limer.sh | Ratio |
|---|---|---|---|
| 1 core · GET round robin · 100 conns | 9,200 req/s | 32,000 req/s | 3.5× |
| 1 core · POST 120 B JSON | 8,600 req/s | 31,000 req/s | 3.6× |
| 1 core · GET · 1000 conns | 7,400 req/s | 26,000 req/s | 3.5× |
| 4 cores · GET round robin · 100 conns | 23,500 req/s | 103,000 req/s | 4.4× |
| 4 cores · POST 120 B JSON | 20,500 req/s | 90,000 req/s | 4.4× |
| 4 cores · GET · 1000 conns | 22,500 req/s | 115,000 req/s | 5.1× |
~5 KB per active connection · one worker can hold ~500,000 connections in ~2.5 GB · reproduce it all with bench/run.sh
Running in under a minute.
No runtime, no modules to install — just libc and whatever optional libraries you have. Linux (epoll).
Build it
Clone and
make. Optional libs (OpenSSL, nghttp2, zstd, brotli) are auto-detected;make installdrops it in/usr/local/bin.Write a Limerfile
One directive per line, blocks in braces — the Caddyfile syntax you already know, with
import, snippets and{$ENV}.Run & reload
limer runin the foreground (perfect under systemd).limer reloadswaps config with zero downtime;limer validatechecks it first.
# build
git clone https://github.com/limer-sh/limer
cd limer && make
# spin up three test backends
python3 tests/backend.py 9001 shard-a &
python3 tests/backend.py 9002 shard-b &
python3 tests/backend.py 9003 shard-c &
# route by payload
./limer run -c examples/payload-routing.Limerfile
curl -X POST localhost:8080/echo \
-d '{"tenant":"acme","event":"signup"}'