A modern, secure, dashboard-first reverse proxy built in Rust
Lorica is a production-ready reverse proxy with a built-in web dashboard, WAF, SLA monitoring, and HTTP caching. One binary, zero external dependencies. Install it, open your browser, and manage everything from the UI - routes, backends, certificates, security rules, and performance metrics.
Built on Cloudflare Pingora, the engine that powers a significant portion of Cloudflare's CDN traffic.
- HTTP/HTTPS reverse proxy with host-based and path-prefix routing
- Path rules - ordered sub-path overrides within a route for backends, cache, headers, rate limits, or direct HTTP status responses
- Header-based routing - per-route rules that pick a backend group by request header value (Exact / Prefix / Regex). A/B testing (
X-Version: beta), multi-tenant isolation (X-Tenant: acme), no upstream URL changes - Canary traffic split - send
X%of requests to an alternate backend group with sticky-per-IP deterministic bucketing. Multiple splits per route; weights capped at 100 cumulative - Response body rewriting - ordered search-and-replace rules (literal or regex with capture groups) applied to upstream response bodies. Configurable content-type filter, max body cap, streams verbatim over the cap
- TLS termination via rustls (no OpenSSL dependency)
- SNI-based certificate selection with wildcard domain support (
*.example.com) - Path rewriting (strip/add prefix, regex with capture groups), hostname aliases, HTTP-to-HTTPS redirect
- Catch-all hostname (
_) as last-resort fallback,redirect_tofor domain redirects,return_statusfor direct responses - gRPC-Web bridge - transparently converts HTTP/1.1 gRPC-web requests to HTTP/2 gRPC for upstream backends
- Maintenance mode - per-route 503 with Retry-After header and custom HTML error page
- Custom error pages - configurable HTML for upstream errors (502/504) with
{{status}}and{{message}}placeholders - Configurable proxy headers, per-route timeouts, WebSocket passthrough, X-Forwarded-Proto via TLS session detection
- Connection pooling with health-aware backend filtering
- WAF engine - 49 OWASP CRS-inspired rules (SQLi, XSS, path traversal, command injection, SSRF, Log4Shell, XXE, CRLF)
- mTLS client verification - per-route CA bundle + optional organization allowlist. Chain validated at the TLS handshake (rustls
WebPkiClientVerifier), per-route enforcement returns 496 ("cert required") or 495 ("cert error").requiredand org-allowlist hot-reload; CA edits take effect on restart - Forward authentication - per-route sub-request to Authelia / Authentik / Keycloak / oauth2-proxy before proxying; 2xx injects response headers into upstream, 401/403/3xx forwarded verbatim to the client, timeout = fail-closed 503. Optional opt-in verdict cache (TTL-capped at 60s, Cookie-keyed) to shortcut hot paths. Under
--workers Nthe cache is owned by the supervisor and routed through the pipelined RPC channel, so an Allow verdict cached by one worker is served from every worker, and a session revocation invalidates the cache uniformly (WPAR-2, design § 7) - Connection pre-filter - global IP allow/deny CIDR policy enforced at TCP accept, before the TLS handshake. Deny always wins; non-empty allow switches to default-deny. Hot-reloaded via arc-swap in single-process and worker modes
- IP blocklist - auto-fetched from Data-Shield IPv4 Blocklist (~80,000 entries, O(1) lookup, updated every 6h)
- Rate limiting - per-route, per-client-IP with configurable RPS and burst tolerance (legacy event-rate estimator
rate_limit_rps/rate_limit_burst) - Per-route token-bucket limiter - exact-semantic admission control via
rate_limit: { capacity, refill_per_sec, scope }. Runs ahead of mTLS / forward-auth / WAF so abusive clients are rejected cheaply with429 Too Many Requests+Retry-After.scope: per_ipisolates individual clients;scope: per_routecaps aggregate traffic to a fragile origin. Cross-worker under--workers N: each worker's CAS-basedLocalBucketcache syncs every 100 ms with the supervisor's authoritative state over a dedicated pipelined RPC channel. Aggregate bound:capacity + 100 ms × N_workers × refill_per_sec(documented indocs/architecture/worker-shared-state.md§ 6) - Auto-ban - IPs that repeatedly exceed rate limits (or trip the WAF) are banned automatically (configurable threshold and duration). Under
--workers N, the WAF auto-ban counter lives in an anonymousmemfdshared by all workers (no UDS round-trip per block), and the supervisor is the sole ban issuer, broadcastingBanIpon threshold crossing - Trusted proxies - CIDR list for X-Forwarded-For validation, prevents IP spoofing via header injection
- DDoS protection - per-route max connections, global flood rate tracking
- Slowloris detection - rejects slow-header attacks with configurable threshold
- Security headers - presets (strict/moderate/none) with HSTS, CSP, X-Frame-Options, X-Content-Type-Options
- HTTP Basic Auth - per-route username/password authentication (Argon2id-hashed) with cached verification
- IP allowlist/denylist and CORS configuration per route
- Certificate export (v1.5.0, disabled by default) - mirror issued certificates as PEM files under
/var/lib/lorica/exported-certs/<hostname>/{cert,chain,fullchain,privkey}.pemevery time a cert is issued or renewed. Lets Ansible / HAProxy sidecar / backup jobs read the live bundle straight off disk without hitting the HTTP API. Atomic writes (.tmpstage +fsync+rename, cross-mountEXDEVfallback), per-filechmod+chownwith configurable owner UID / group GID / octal modes (defaults 0o640 files / 0o750 dirs), fail-soft (export error never blocks the ACME renewal). Per-pattern ACL table narrows which hostnames are exported and with which UID / GID (exact match, leading*.wildcard, or bare*). Audit-logged + rate-limitedGET /api/v1/certificates/:id/downloadcomplements on-disk export for one-off downloads. Threat model:docs/security/cert-export-threat-model.md
- Passive SLA - per-route uptime, latency percentiles (p50/p95/p99), rolling windows (1h/24h/7d/30d)
- Active SLA - synthetic HTTP probes at configurable intervals, detects outages during low-traffic periods
- Prometheus metrics -
/metricsendpoint with request counts, latency histograms, backend health, WAF events, cert expiry. Per-feature counters for cache-predictor bypass, header-rule matches, canary split selection, mirror outcomes (spawned / dropped / errored), forward-auth verdict cache hit rate - all bounded by route count. Under--workers Nevery scrape triggers a pull-on-scrape fan-out over the pipelined RPC channel so per-worker counters are sub-second fresh; concurrent scrapes dedup into a single fan-out and stuck workers fall back to cached state within a 500 ms per-worker timeout (WPAR-7) - Request mirroring (shadow testing) - duplicate every request to one or more secondary backends (deterministic per
X-Request-Idsampling, 256-slot concurrency cap, body mirroring up to a configurable cap). Fire-and-forget: mirror failure can never impact the primary - Real-time access logs - WebSocket streaming to the dashboard with filtering
- Load testing - built-in load test engine with SSE streaming, cron scheduling, CPU circuit breaker, and result comparison
- SLA breach alerts - automatic notifications when SLA drops below target
- Web dashboard - Svelte 5 UI (~59 KB) embedded in the binary: routes, backends, certs, WAF, SLA, load tests, settings
- REST API - full CRUD for all entities, session-based auth, rate-limited login
- TOML config export/import - with diff preview before applying changes
- Nginx config import - paste an
nginx.confto auto-create routes, backends, certificates, and path rules with cert import support - ACME / Let's Encrypt - automatic TLS provisioning via HTTP-01 and DNS-01 challenges (Cloudflare, Route53, OVH providers), multi-domain SAN and wildcard support, smart auto-renewal, OCSP stapling
- DNS providers - global DNS credentials configured once in Settings and referenced by ID for certificate provisioning (Cloudflare, Route53, OVH)
- Notification channels - stdout, SMTP email, HTTP webhook, Slack with per-channel rate limiting
- Ban list management - view and unban auto-banned IPs from the dashboard
- Pingora engine - forked from Cloudflare's production proxy framework
- HTTP cache - in-memory response caching with LRU eviction (128 MiB cap), TinyUFO algorithm, cache lock (thundering herd protection), stale-while-revalidate with background refresh (serves stale immediately, fetches fresh in parallel) and stale-if-error, HTTP PURGE method support
- Cache Vary - per-route
cache_vary_headerspartitions the cache by request-header values (e.g.Accept-Encoding) merged with the origin'sVaryresponse;Vary: *anchors on URI to bound cardinality - Cache predictor - 16-shard LRU (32K keys) remembers deterministically-uncacheable responses and short-circuits the cache state machine on subsequent hits, avoiding cache-lock contention on known-bypass traffic
- Peak EWMA load balancing - latency-aware backend selection alongside Round Robin, Consistent Hash, Random, Least Connections
- DashMap - lock-free concurrent reads for ban list and route connections in the hot path
- Sub-0.5ms WAF evaluation - precompiled regex patterns with zero overhead when disabled
- Worker process isolation - fork+exec with socket passing via SCM_RIGHTS
- Protobuf command channel - supervisor-to-worker config reload without traffic interruption. Under
--workers N, reloads run as two-phase Prepare + Commit on a pipelined RPC channel so the divergence window between workers collapses to the UDS RTT (microseconds) instead of the per-worker DB-rebuild time (WPAR-8, design § 7). The same RPC plane carries cross-worker circuit-breaker admission (BreakerDecision::AllowProbefor HalfOpen) so probe slots are allocated atomically across workers and a failure on one trips the breaker for every worker (WPAR-3) - Health checks - TCP and HTTP probes, backends marked degraded (>2s) or down and removed from rotation
- Graceful drain - per-backend active connection tracking with Closing/Closed lifecycle states
- Certificate hot-swap - atomic swap via arc-swap, zero downtime during rotation
- Encrypted storage - AES-256-GCM encryption for certificate private keys at rest
# Download the latest release
wget https://github.com/Rwx-G/Lorica/releases/latest/download/lorica.deb
sudo dpkg -i lorica.debThe package creates a lorica user, installs a systemd service (enabled by default), and starts Lorica on ports 8080 (HTTP), 8443 (HTTPS), and 9443 (dashboard).
To customize ports, workers, or log level, edit the systemd unit:
sudo systemctl edit lorica[Service]
ExecStart=
ExecStart=/usr/bin/lorica --data-dir /var/lib/lorica \
--http-port 80 --https-port 443 --management-port 9443 \
--workers 4 --log-level infosudo systemctl restart loricaLorica is Linux-only, and its management API (dashboard) binds to 127.0.0.1
inside the container. Publishing the port with -p 9443:9443 therefore
does not expose the dashboard to the host. The simplest fix on Linux is to
share the host network with the container:
docker build -t lorica .
docker run -d --name lorica --network host \
-v lorica-data:/var/lib/lorica loricaWith --network host the dashboard is reachable at http://127.0.0.1:9443
(HTTP, not HTTPS) and the proxy listeners bind straight onto the host —
:8080 (HTTP) and :8443 (HTTPS) — so no -p flags are needed.
Get the first-run admin password (printed to stdout once and persisted to a 0600 file inside the container):
docker exec lorica cat /var/lib/lorica/initial-admin-password
# Fallback only if the file write failed (password printed to stdout instead):
docker logs lorica 2>&1 | grep 'Initial admin password:'Open http://127.0.0.1:9443 in your browser and log in with admin + the
password. You will be prompted to change it on first login.
Why not
-p 9443:9443? The management server binds127.0.0.1only (seelorica-api/src/server.rs), so port publishing cannot reach it. Use--network hostfor local dev. For a remote server where host networking is not an option, expose the dashboard via an SSH tunnel or Lorica's own self-proxy — see docs/self-proxy-dashboard.md.
The dashboard and REST API bind to 127.0.0.1 by design, not by accident.
That keeps the management plane off the network so it cannot be exposed to the
web, even through misconfiguration. Binding to 0.0.0.0 is explicitly out of
scope — it would weaken this security boundary.
On a production host, reach the dashboard through an SSH tunnel:
ssh -L 9443:127.0.0.1:9443 user@hostThen open http://127.0.0.1:9443 locally. For TLS-encrypted remote access via
the proxy itself (not recommended for production), see
docs/self-proxy-dashboard.md.
lorica --data-dir /var/lib/loricaOpen http://127.0.0.1:9443 in your browser (loopback-only by design — use an
SSH tunnel on remote hosts; see above). On first run, a random admin password
is written to <data-dir>/initial-admin-password (mode 0600).
lorica [OPTIONS]
Options:
--data-dir <PATH> Data directory (default: /var/lib/lorica)
--management-port <PORT> Dashboard/API port (default: 9443)
--http-port <PORT> HTTP proxy port (default: 8080)
--https-port <PORT> HTTPS proxy port (default: 8443)
--workers <N> Worker processes (default: 0 = single-process)
--log-level <LEVEL> Log level (default: info)
--log-format <FORMAT> Log format: json (default) or text
--log-file <PATH> Log to file (in addition to stdout)
--version Print version
The dashboard ships inside the binary and is served on the management port (default 9443). No separate frontend server, no npm, no build step - just open your browser.
Getting started guide with interactive setup checklist
Overview cockpit with system health, routes, security, and performance at a glance
Routes table with hostname, backends, WAF mode, health status, and TLS
Route editor with 50+ settings across 7 tabs (General, Routing, Transform, Protection, Security, Cache, Upstream)
49 WAF rules with per-rule toggle, covering SQLi, XSS, SSRF, Log4Shell, XXE, and more
System page with worker health, heartbeat latency, CPU/memory gauges, and process metrics
- Overview - cockpit dashboard with section helpers, setup checklist, system/route/security/performance cards
- Routes - create/edit routes with host matching, path prefixes, load balancing, WAF mode, rate limits, caching, timeouts, security headers, CORS, and 25 other per-route settings
- Backends - manage backend addresses, weights, health check type (TCP/HTTP), TLS upstream, active connections
- Certificates - upload PEM certificates, view expiry dates, provision via ACME/Let's Encrypt (HTTP-01, DNS-01)
- Security - WAF event table with category filtering, 49 rule toggles, IP ban list with unban button
- SLA - per-route passive/active SLA side-by-side, latency percentile tables, config editor, CSV/JSON export
- Load Tests - test config management with clone, one-click execution, real-time SSE progress panel, historical results
- Active Probes - CRUD for synthetic health probes with route selection, HTTP method/path/status/interval/timeout
- Access Logs - scrollable real-time log stream via WebSocket with green pulsing indicator
- System - worker table with PID, health, heartbeat latency; CPU/memory/disk gauges
- Settings - notification channels, security header presets, DNS providers (Cloudflare / Route53 / OVH), ban rules, OpenTelemetry exporter, GeoIP / ASN databases, certificate filesystem export zone + ACL editor, config export / import with diff preview
- Theme - light/dark mode toggle
Lorica is a Rust workspace with 28 crates: 16 forked from Cloudflare Pingora and 12 product crates. See FORK.md for the full fork lineage and renaming rules.
| Crate | Purpose |
|---|---|
lorica |
CLI binary, supervisor, worker orchestration |
lorica-proxy |
HTTP/HTTPS proxy engine (Pingora fork) |
lorica-tls |
SNI certificate resolver, hot-swap, ACME |
lorica-config |
SQLite store, migrations, TOML export/import |
lorica-api |
axum REST API, auth, session management |
lorica-dashboard |
Svelte 5 frontend embedded via rust-embed |
lorica-waf |
WAF engine, OWASP rules, IP blocklist |
lorica-notify |
Alert dispatch (stdout, SMTP, webhook) |
lorica-bench |
SLA monitoring, load testing engine |
lorica-worker |
fork+exec worker isolation, typed FD passing (Listener / Shmem / Rpc) |
lorica-command |
Protobuf supervisor-worker command channel + pipelined RpcEndpoint (Envelope framing, in-flight demux, bounded backpressure), Coalescer, GenerationGate |
lorica-shmem |
Anonymous memfd region shared across all workers; AtomicHashTable for per-IP WAF flood / auto-ban counters; SipHash-1-3 anti-HashDoS; 5-min eviction walker |
lorica-lb |
Load balancing (Round Robin, Peak EWMA, Hash, Random, Least Conn) |
lorica-cache |
HTTP response cache, LRU eviction |
lorica-limits |
Rate estimator + per-route LocalBucket / AuthoritativeBucket token-bucket primitives (lock-free CAS, 100 ms cross-worker sync) |
Data plane (proxy) and control plane (API/dashboard) are fully separated. API mutations trigger config reload via arc-swap - the proxy picks up changes without restarting.
Measured on a single Linux VM (4 vCPU, 8 GB RAM):
| Metric | Value |
|---|---|
| Single-process throughput | ~6,500 req/s |
| Multi-worker throughput (4 workers) | ~25,000 req/s |
| WAF evaluation latency | < 0.5 ms per request |
| WAF overhead on throughput | ~6% |
| Dashboard bundle size | ~59 KB (gzipped) |
| Config reload | Zero-downtime (arc-swap) |
| Certificate hot-swap | Zero-downtime (atomic) |
Create a route via the REST API:
# Authenticate
TOKEN=$(curl -s http://127.0.0.1:9443/api/v1/auth/login \
-H 'Content-Type: application/json' \
-d '{"username":"admin","password":"your-admin-password"}' \
-c - | grep session | awk '{print $NF}')
# Create a backend
curl -s http://127.0.0.1:9443/api/v1/backends \
-b "session=$TOKEN" \
-H 'Content-Type: application/json' \
-d '{
"address": "127.0.0.1:8080",
"health_check_interval_s": 10,
"health_check_type": "http",
"health_check_path": "/healthz"
}'
# Create a route
curl -s http://127.0.0.1:9443/api/v1/routes \
-b "session=$TOKEN" \
-H 'Content-Type: application/json' \
-d '{
"hostname": "app.example.com",
"path_prefix": "/",
"backend_ids": [1],
"load_balancing": "peak_ewma",
"tls_enabled": true,
"certificate_id": 1,
"waf_enabled": true,
"waf_mode": "block",
"rate_limit_rps": 100,
"rate_limit_burst": 50,
"cache_enabled": true,
"cache_ttl_s": 300,
"force_https": true,
"security_headers": "strict"
}'Or just use the dashboard - it covers all the same operations with zero curl.
All endpoints are served on the management port (default 9443) over HTTP (loopback only). Protected endpoints require a session cookie obtained via /api/v1/auth/login.
| Method | Path | Description |
|---|---|---|
POST |
/api/v1/auth/login |
Authenticate (returns session cookie) |
POST |
/api/v1/auth/logout |
Invalidate session |
GET |
/metrics |
Prometheus metrics (no auth) |
GET |
/.well-known/acme-challenge/:token |
ACME HTTP-01 challenge response |
| Method | Path | Description |
|---|---|---|
GET |
/api/v1/routes |
List all routes |
POST |
/api/v1/routes |
Create route |
GET |
/api/v1/routes/:id |
Get route |
PUT |
/api/v1/routes/:id |
Update route |
DELETE |
/api/v1/routes/:id |
Delete route |
POST |
/api/v1/validate/mtls-pem |
Parse a candidate client-CA PEM and return per-cert subjects |
POST |
/api/v1/validate/forward-auth |
Probe a candidate forward-auth URL (one GET, status + elapsed) |
| Method | Path | Description |
|---|---|---|
GET |
/api/v1/backends |
List all backends |
POST |
/api/v1/backends |
Create backend |
GET |
/api/v1/backends/:id |
Get backend |
PUT |
/api/v1/backends/:id |
Update backend |
DELETE |
/api/v1/backends/:id |
Delete backend |
| Method | Path | Description |
|---|---|---|
GET |
/api/v1/certificates |
List certificates |
POST |
/api/v1/certificates |
Upload PEM certificate |
POST |
/api/v1/certificates/self-signed |
Generate self-signed certificate |
GET |
/api/v1/certificates/:id |
Get certificate |
GET |
/api/v1/certificates/:id/download?part={cert|key|chain|bundle} |
Download PEM material (rate-limited, audit-logged) |
PUT |
/api/v1/certificates/:id |
Update certificate |
DELETE |
/api/v1/certificates/:id |
Delete certificate |
GET |
/api/v1/cert-export/acls |
List per-pattern cert-export ACLs |
POST |
/api/v1/cert-export/acls |
Create a cert-export ACL rule |
DELETE |
/api/v1/cert-export/acls/:id |
Delete a cert-export ACL rule |
POST |
/api/v1/cert-export/reapply |
Re-export every certificate to disk |
GET |
/api/v1/cert-export/orphans |
List per-hostname subdirectories with no matching live cert |
DELETE |
/api/v1/cert-export/orphans/:name |
Remove one orphan subdirectory (sanitised + live-cert guard) |
| Method | Path | Description |
|---|---|---|
POST |
/api/v1/acme/provision |
Provision via HTTP-01 |
POST |
/api/v1/acme/provision-dns |
Provision via DNS-01 |
POST |
/api/v1/acme/provision-dns-manual |
Start manual DNS-01 flow |
POST |
/api/v1/acme/provision-dns-manual/confirm |
Confirm manual DNS-01 |
| Method | Path | Description |
|---|---|---|
GET |
/api/v1/waf/events |
Recent WAF events (with category filter) |
DELETE |
/api/v1/waf/events |
Clear WAF events |
GET |
/api/v1/waf/stats |
WAF statistics |
GET |
/api/v1/waf/rules |
List WAF rules |
PUT |
/api/v1/waf/rules/:id |
Enable/disable rule |
GET |
/api/v1/waf/rules/custom |
List custom rules |
POST |
/api/v1/waf/rules/custom |
Create custom rule |
DELETE |
/api/v1/waf/rules/custom/:id |
Delete custom rule |
GET |
/api/v1/waf/blocklist |
Blocklist status |
PUT |
/api/v1/waf/blocklist |
Enable/disable blocklist |
POST |
/api/v1/waf/blocklist/reload |
Reload blocklist |
| Method | Path | Description |
|---|---|---|
GET |
/api/v1/sla/overview |
SLA overview for all routes |
GET |
/api/v1/sla/routes/:id |
SLA metrics for route |
GET |
/api/v1/sla/routes/:id/buckets |
Time-bucketed SLA data |
GET |
/api/v1/sla/routes/:id/config |
SLA config |
PUT |
/api/v1/sla/routes/:id/config |
Update SLA config |
GET |
/api/v1/sla/routes/:id/export |
Export SLA data (CSV/JSON) |
GET |
/api/v1/sla/routes/:id/active |
Active probe results |
GET |
/api/v1/probes |
List probes |
POST |
/api/v1/probes |
Create probe |
GET |
/api/v1/probes/route/:route_id |
Probes for route |
PUT |
/api/v1/probes/:id |
Update probe |
DELETE |
/api/v1/probes/:id |
Delete probe |
| Method | Path | Description |
|---|---|---|
GET |
/api/v1/loadtest/configs |
List configs |
POST |
/api/v1/loadtest/configs |
Create config |
PUT |
/api/v1/loadtest/configs/:id |
Update config |
DELETE |
/api/v1/loadtest/configs/:id |
Delete config |
POST |
/api/v1/loadtest/configs/:id/clone |
Clone config |
POST |
/api/v1/loadtest/start/:config_id |
Start test (requires confirm) |
POST |
/api/v1/loadtest/start/:config_id/confirm |
Confirm and execute |
GET |
/api/v1/loadtest/status |
Current test status |
GET |
/api/v1/loadtest/ws |
WebSocket real-time progress |
POST |
/api/v1/loadtest/abort |
Abort running test |
GET |
/api/v1/loadtest/results/:config_id |
Test results |
GET |
/api/v1/loadtest/results/:config_id/compare |
Compare runs |
| Method | Path | Description |
|---|---|---|
DELETE |
/api/v1/cache/routes/:id |
Purge route cache |
GET |
/api/v1/cache/stats |
Cache hit/miss stats |
GET |
/api/v1/bans |
List banned IPs |
DELETE |
/api/v1/bans/:ip |
Unban IP |
| Method | Path | Description |
|---|---|---|
PUT |
/api/v1/auth/password |
Change password |
GET |
/api/v1/settings |
Global settings |
PUT |
/api/v1/settings |
Update settings |
GET |
/api/v1/status |
System status summary |
GET |
/api/v1/system |
CPU, memory, disk usage |
GET |
/api/v1/workers |
Worker heartbeat metrics |
GET |
/api/v1/logs |
Access logs |
DELETE |
/api/v1/logs |
Clear logs |
GET |
/api/v1/logs/ws |
WebSocket log stream |
POST |
/api/v1/config/export |
Export config as TOML |
POST |
/api/v1/config/import |
Import TOML config |
POST |
/api/v1/config/import/preview |
Preview import diff |
GET |
/api/v1/notifications |
List notification configs |
POST |
/api/v1/notifications |
Create notification config |
PUT |
/api/v1/notifications/:id |
Update notification config |
DELETE |
/api/v1/notifications/:id |
Delete notification config |
POST |
/api/v1/notifications/:id/test |
Test notification channel |
GET |
/api/v1/preferences |
List user preferences |
PUT |
/api/v1/preferences/:id |
Update preference |
DELETE |
/api/v1/preferences/:id |
Delete preference |
# Prerequisites
# - Rust 1.88+
# - Node.js 20+ (Vite 8 minimum, dashboard compilation)
# - Linux (x86_64)
git clone https://github.com/Rwx-G/Lorica.git
cd Lorica
cargo build --release
# Binary is at target/release/lorica
# The Svelte frontend is compiled automatically during cargo build.# All Rust unit tests (~2091 tests across 28 crates)
cargo test --workspace
# Product crate tests only (~1306 tests - lorica-native, include
# the v1.5.0 hardening coverage : rate-limit buckets, body-size
# layers, session rotation, public_version masking, ammonia bypass
# corpus, map_err context preservation, cert-export orphan sweep)
cargo test -p lorica-config -p lorica-api -p lorica -p lorica-waf \
-p lorica-notify -p lorica-bench -p lorica-worker \
-p lorica-command -p lorica-limits -p lorica-shmem \
-p lorica-challenge -p lorica-geoip
# Pingora-forked crate tests (~690 tests)
cargo test -p lorica-core -p lorica-proxy -p lorica-http \
-p lorica-error -p lorica-tls -p lorica-cache \
-p lorica-pool -p lorica-runtime -p lorica-timeout \
--features ring -p lorica-lb
# End-to-end tests driving a real Pingora Server (95 tests, 16 binaries)
cargo test -p lorica --test bot_rpc_cache_e2e_test \
--test canary_e2e_test \
--test config_reload_rpc_e2e_test \
--test connection_filter_test \
--test forward_auth_e2e_test \
--test header_routing_e2e_test \
--test metrics_pull_rpc_e2e_test \
--test mirror_e2e_test \
--test mtls_e2e_test \
--test proxy_config_test \
--test proxy_routing_test \
--test rate_limit_e2e_test \
--test rate_limit_sync_e2e_test \
--test response_rewrite_e2e_test \
--test swr_e2e_test \
--test verdict_breaker_rpc_e2e_test
# Frontend tests (320 Vitest tests across 9 files)
cd lorica-dashboard/frontend && npx vitest run| Layer | Count | Notes |
|---|---|---|
| Product unit (config, api, lib, waf, notify, bench, worker, command, limits, challenge, shmem, geoip) | ~1306 | Lorica-specific code, including v1.5.0 hardening coverage (ammonia bypass corpus, named rate-limit buckets with Retry-After, per-route body-size 413 path, session rotation integration test, public_version masking, map_err context preservation, cert-export orphan sweep + path-traversal rejection). The verdict-cache global-state race (backlog #16) is fixed in v1.5.0 via serial_test so the full suite runs parallel. |
Product e2e (real Pingora Server + mock backends) |
95 | 16 binaries: mTLS, response rewriting, mirroring, forward auth, SWR, connection filter, canary, header routing, config, routing, rate-limit (legacy + sync), bot RPC cache, config-reload RPC, metrics-pull RPC, verdict-breaker RPC |
| Pingora-forked crates (core, proxy, http, error, tls, cache, pool, runtime, timeout, lb, ketama, lru, memory-cache, limits, header-serde, tinyufo) | ~690 | Inherited upstream coverage kept passing on every change, plus Lorica-added regression tests in lorica-core (396) and lorica-cache (103) |
| Frontend (vitest / svelte-check) | 320 | Form validation, type safety, component wiring |
| Total shipping tests | ~2411 |
tests-e2e-docker/ spins Lorica up against real backend containers
and drives 660+ assertions through the actual network stack:
cd tests-e2e-docker
./run.sh # single-process (429 asserts) + workers mode (109) + cert-export (26)
docker compose --profile bot run --rm bot-smoke # 29 asserts - graded bot challenge
docker compose --profile bot-workers run --rm bot-smoke-workers # 29 asserts - same under --workers 2
docker compose --profile geoip run --rm geoip-smoke # 16 asserts - country allow/deny
docker compose --profile rdns run --rm rdns-smoke # 7 asserts - forward-confirmed rDNS bypass
docker compose --profile otel run --rm otel-smoke # 16 asserts - OTLP + W3C + log/trace correlation
docker compose --profile otel-workers run --rm otel-smoke-workers # 16 asserts - same under --workers 2
docker compose --profile cert-export run --rm cert-export-smoke # 26 asserts - PEM disk export + ACL + reapplyThe two intentional gaps in the Docker harness are:
- mTLS client-cert handshake (495 / 496 / 200 based on the
presented cert). The e2e container starts without any TLS certs
pre-loaded so the HTTPS listener on port 8443 is never built -
driving
curl --certneeds a staged environment. The config surface (CA PEM validation,required+allowed_organizationshot-reload) is covered. - Connection pre-filter TCP drop (scanner IP refused at
accept()before TLS). The test-runner sits on the same Docker network as Lorica, so any CIDR that would cover a real scanner also covers the runner - asserting the drop from inside would be self-blocking. The config round-trip (valid CIDR accepted, garbage rejected 400) is covered.
Validate both manually on staging when touching the surrounding code paths.
The .deb and .rpm packages install a hardened systemd unit with:
ProtectSystem=strict,PrivateTmp=yes,NoNewPrivileges=yesMemoryDenyWriteExecute=yes,SystemCallFilter=@system-serviceRestrictNamespaces=yes,RestrictSUIDSGID=yes- Runs as dedicated
loricauser withCAP_NET_BIND_SERVICE - Service auto-starts on install and auto-restarts on upgrade
- Data directory (
/var/lib/lorica) preserved across upgrades
Customize the service (e.g. enable workers) via drop-in override:
sudo systemctl edit lorica[Service]
ExecStart=
ExecStart=/usr/bin/lorica --workers 6See docs/tuning.md for kernel parameters (sysctl), file descriptor limits, worker configuration, cache settings, and a production readiness checklist. Run bench/ for reproducible throughput measurements.
When running with --workers N >= 1, see docs/worker-mode.md for the operational notes (which settings require a supervisor restart, what changes between single-process and worker mode).
Release .deb and .rpm packages are GPG-signed. Import the public key to verify:
curl -fsSL https://github.com/Rwx-G/Lorica/raw/main/docs/lorica-signing-key.asc | sudo gpg --dearmor -o /usr/share/keyrings/lorica.gpg
gpg --verify lorica.deb.asc lorica.deb| Version | Features | Status |
|---|---|---|
| v1.4.0 | OpenTelemetry tracing (OTLP), GeoIP country blocking, Bot protection (PoW / captcha / cookie with 5-category bypass matrix) | Shipped |
| v1.5.0 | Operator-input guard-rails on every field with blur + input inline errors; Route group_name + filter + colored pill; Certificate download API + dashboard split-menu with private-key confirm; Filesystem certificate export zone with per-pattern ACL, Settings tab, operator re-export endpoint, orphan sweep + per-row delete; Path-rule redirect fix ; Security hardening wave: ammonia HTML sanitiser, per-endpoint rate limits on management plane, per-route body-size limits with 1 MiB global default, session cookie rotation on password change, /system response filter, rustls-pemfile → rustls-pki-types migration, rand 0.9 bump, source-error preservation on .map_err chains, WebSocket log-stream backpressure with close-on-slow-client ; Doc coverage pass + #![warn(missing_docs)] on every Lorica-native crate ; ACME unit tests (wiremock on Cloudflare + OVH challengers, is_valid_dns_server shell-filter, pure should_auto_renew predicate) ; verdict_cache test-parallelism race fixed via serial_test |
Shipped |
| v1.5.1 + v1.5.2 (audit-closure cycles) | Worker-mode cert hot-reload (cert install / renew now serves new cert across all workers without restart) ; SMTP encryption modes (starttls / tls / none) for the Email notification channel ; security defense-in-depth pass : webhook URL + Slack URL + auth_header scrubbed on JSON GET (matched the v1.5.1 TOML scrub asymmetry), CSV formula injection guard on access-log export, CSP3 directives (frame-ancestors, form-action, base-uri, object-src), per-endpoint rate limits broadened to ~16 mutating endpoints, redirect-policy=none on webhook / OCSP / blocklist clients ; reactor-stall pass : LogStore + enforce_notification_retention off-loaded to spawn_blocking ; reload pass : two-phase + legacy converged through one apply_per_process_resolver_hooks helper, cert-resolver reload serialised, OTel / GeoIP / ASN apply-error counter ; perf : Cow URL decode + itoa status formatting + chrono deferred until WAF match + dashmap fast-path on bot stash + parking_lot::Mutex on hot path + RuleSet::matches prefilter shortcut ; deps : rustls-webpki 0.103.13 (RUSTSEC-2026-0104 + 0099), postcss 8.5.10 (CVE-2026-41305), aws-lc-rs dropped from the lorica-tls crypto stack in favor of ring (the broader binary still pulls aws-lc-rs transitively via the rustls 0.23 default stack in lorica-api), x509-parser 0.18 aligned across lorica-tls / lorica-api ; chore : ~50 magic-number bl() / rl() calls in server.rs lifted to pub const, 3 formatBytes dashboard implementations consolidated into lib/format.ts, 3 docs/security.md drift items fixed (49 WAF rules + ~80k IP blocklist) |
Shipped |
| v1.6.0 | AI-crawler (LLM) deny-list as a first-class feature (known-bot User-Agent + rDNS matcher, per-route opt-in / opt-out, Prometheus counter), Hot binary upgrade (zero-downtime restart), Team settings (multiple users, roles, RBAC) ; proxy_wiring.rs + main.rs module split |
Planned |
| v2.0.0 | HTTP/3 (QUIC), TCP/L4 proxying | Planned |
rustls-pemfileremoval in thelorica-tlsfork. RUSTSEC-2025-0134 (unmaintained) still shows transitively through our Pingora fork. Native Lorica code migrated torustls-pki-typesin v1.5.0 ; the transitive dep clears once Pingora upstream migrates.rand 0.8removal in forked crates. Native Lorica code bumped torand 0.9in v1.5.0. RUSTSEC-2026-0097 (unsound with custom logger) was cleared in v1.5.8 by bumping the transitive0.8line to0.8.6; removing the0.8line entirely still depends on the upstream forks (lorica-runtime,lorica-limits) and theaxum/tungstenitemajors. Same monitoring as therustls-pemfilerow.
The table above tracks feature milestones; the current release and the patch cycles (v1.5.3+) live in CHANGELOG.md.
See COMPARISON.md for a detailed feature comparison with Nginx, Traefik, HAProxy, Caddy, BunkerWeb, Sozu, and Pingora.
| Feature | Status | Rationale |
|---|---|---|
| HTTP/3 / QUIC | Planned | Waiting for Pingora PR #524 (tokio-quiche integration) to merge upstream |
| io_uring | Not planned | tokio-uring is unmaintained since 2022. epoll via Tokio delivers sufficient performance (40M req/s at Cloudflare scale) |
| Windows / macOS | Not supported | Linux x86_64 only (fork+exec worker model requires Linux) |
| OpenSSL / BoringSSL | Removed | rustls is the sole TLS provider |
Apache-2.0 - see LICENSE.
Built on Pingora by Cloudflare (Apache-2.0). See NOTICE and FORK.md for fork details.
Author: Rwx-G
