{"id":47740620,"url":"https://github.com/rwx-g/lorica","last_synced_at":"2026-06-10T13:01:00.129Z","repository":{"id":347956269,"uuid":"1195330568","full_name":"Rwx-G/Lorica","owner":"Rwx-G","description":"A modern, secure, dashboard-first reverse proxy built in Rust. Single binary, embedded control plane, optional WAF. Powered by Pingora. ","archived":false,"fork":false,"pushed_at":"2026-05-10T16:45:51.000Z","size":8165,"stargazers_count":23,"open_issues_count":0,"forks_count":3,"subscribers_count":2,"default_branch":"main","last_synced_at":"2026-05-10T17:33:08.879Z","etag":null,"topics":["control-plane","reverse-proxy","sysops","waf"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Rwx-G.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":"NOTICE","maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-29T14:40:43.000Z","updated_at":"2026-05-10T15:12:41.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/Rwx-G/Lorica","commit_stats":null,"previous_names":["rwx-g/lorica"],"tags_count":15,"template":false,"template_full_name":null,"purl":"pkg:github/Rwx-G/Lorica","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Rwx-G%2FLorica","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Rwx-G%2FLorica/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Rwx-G%2FLorica/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Rwx-G%2FLorica/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Rwx-G","download_url":"https://codeload.github.com/Rwx-G/Lorica/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Rwx-G%2FLorica/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34153483,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-10T02:00:07.152Z","response_time":89,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["control-plane","reverse-proxy","sysops","waf"],"created_at":"2026-04-02T23:44:00.708Z","updated_at":"2026-06-10T13:01:00.119Z","avatar_url":"https://github.com/Rwx-G.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cimg src=\"docs/assets/lorica-logo.png\" alt=\"Lorica\" width=\"128\"\u003e\n  \u003ch1 align=\"center\"\u003eLorica\u003c/h1\u003e\n  \u003cp align=\"center\"\u003e\u003cstrong\u003eA modern, secure, dashboard-first reverse proxy built in Rust\u003c/strong\u003e\u003c/p\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"LICENSE\"\u003e\u003cimg src=\"https://img.shields.io/badge/license-Apache%202.0-blue.svg\" alt=\"License\"\u003e\u003c/a\u003e\n  \u003cimg src=\"https://img.shields.io/badge/version-1.5.10-brightgreen.svg\" alt=\"Version\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/Rust-2024-orange.svg\" alt=\"Rust\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/Platform-Linux-0078D6.svg\" alt=\"Platform\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/Lorica%20Tests-1306%2B-brightgreen.svg\" alt=\"Lorica Tests\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/Pingora%20Tests-690-blue.svg\" alt=\"Inherited Tests\"\u003e\n\u003c/p\u003e\n\n---\n\nLorica 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.\n\nBuilt on [Cloudflare Pingora](https://github.com/cloudflare/pingora), the engine that powers a significant portion of Cloudflare's CDN traffic.\n\n## Key Features\n\n### :shield: Proxy \u0026 Routing\n\n- HTTP/HTTPS reverse proxy with host-based and path-prefix routing\n- **Path rules** - ordered sub-path overrides within a route for backends, cache, headers, rate limits, or direct HTTP status responses\n- **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\n- **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\n- **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\n- TLS termination via rustls (no OpenSSL dependency)\n- SNI-based certificate selection with wildcard domain support (`*.example.com`)\n- Path rewriting (strip/add prefix, regex with capture groups), hostname aliases, HTTP-to-HTTPS redirect\n- Catch-all hostname (`_`) as last-resort fallback, `redirect_to` for domain redirects, `return_status` for direct responses\n- **gRPC-Web bridge** - transparently converts HTTP/1.1 gRPC-web requests to HTTP/2 gRPC for upstream backends\n- **Maintenance mode** - per-route 503 with Retry-After header and custom HTML error page\n- **Custom error pages** - configurable HTML for upstream errors (502/504) with `{{status}}` and `{{message}}` placeholders\n- Configurable proxy headers, per-route timeouts, WebSocket passthrough, X-Forwarded-Proto via TLS session detection\n- Connection pooling with health-aware backend filtering\n\n### :lock: Security\n\n- **WAF engine** - 49 OWASP CRS-inspired rules (SQLi, XSS, path traversal, command injection, SSRF, Log4Shell, XXE, CRLF)\n- **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\"). `required` and org-allowlist hot-reload; CA edits take effect on restart\n- **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 N` the 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)\n- **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\n- **IP blocklist** - auto-fetched from Data-Shield IPv4 Blocklist (~80,000 entries, O(1) lookup, updated every 6h)\n- **Rate limiting** - per-route, per-client-IP with configurable RPS and burst tolerance (legacy event-rate estimator `rate_limit_rps` / `rate_limit_burst`)\n- **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 with `429 Too Many Requests` + `Retry-After`. `scope: per_ip` isolates individual clients; `scope: per_route` caps aggregate traffic to a fragile origin. Cross-worker under `--workers N`: each worker's CAS-based `LocalBucket` cache 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 in `docs/architecture/worker-shared-state.md` § 6)\n- **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 anonymous `memfd` shared by all workers (no UDS round-trip per block), and the supervisor is the sole ban issuer, broadcasting `BanIp` on threshold crossing\n- **Trusted proxies** - CIDR list for X-Forwarded-For validation, prevents IP spoofing via header injection\n- **DDoS protection** - per-route max connections, global flood rate tracking\n- **Slowloris detection** - rejects slow-header attacks with configurable threshold\n- **Security headers** - presets (strict/moderate/none) with HSTS, CSP, X-Frame-Options, X-Content-Type-Options\n- **HTTP Basic Auth** - per-route username/password authentication (Argon2id-hashed) with cached verification\n- **IP allowlist/denylist** and **CORS configuration** per route\n- **Certificate export** (v1.5.0, disabled by default) - mirror issued certificates as PEM files under `/var/lib/lorica/exported-certs/\u003chostname\u003e/{cert,chain,fullchain,privkey}.pem` every 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 (`.tmp` stage + `fsync` + `rename`, cross-mount `EXDEV` fallback), per-file `chmod` + `chown` with 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-limited `GET /api/v1/certificates/:id/download` complements on-disk export for one-off downloads. Threat model: `docs/security/cert-export-threat-model.md`\n\n### :bar_chart: Monitoring \u0026 Observability\n\n- **Passive SLA** - per-route uptime, latency percentiles (p50/p95/p99), rolling windows (1h/24h/7d/30d)\n- **Active SLA** - synthetic HTTP probes at configurable intervals, detects outages during low-traffic periods\n- **Prometheus metrics** - `/metrics` endpoint 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 N` every 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)\n- **Request mirroring (shadow testing)** - duplicate every request to one or more secondary backends (deterministic per `X-Request-Id` sampling, 256-slot concurrency cap, body mirroring up to a configurable cap). Fire-and-forget: mirror failure can never impact the primary\n- **Real-time access logs** - WebSocket streaming to the dashboard with filtering\n- **Load testing** - built-in load test engine with SSE streaming, cron scheduling, CPU circuit breaker, and result comparison\n- **SLA breach alerts** - automatic notifications when SLA drops below target\n\n### :globe_with_meridians: Management\n\n- **Web dashboard** - Svelte 5 UI (~59 KB) embedded in the binary: routes, backends, certs, WAF, SLA, load tests, settings\n- **REST API** - full CRUD for all entities, session-based auth, rate-limited login\n- **TOML config export/import** - with diff preview before applying changes\n- **Nginx config import** - paste an `nginx.conf` to auto-create routes, backends, certificates, and path rules with cert import support\n- **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\n- **DNS providers** - global DNS credentials configured once in Settings and referenced by ID for certificate provisioning (Cloudflare, Route53, OVH)\n- **Notification channels** - stdout, SMTP email, HTTP webhook, Slack with per-channel rate limiting\n- **Ban list management** - view and unban auto-banned IPs from the dashboard\n\n### :zap: Performance\n\n- **Pingora engine** - forked from Cloudflare's production proxy framework\n- **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\n- **Cache Vary** - per-route `cache_vary_headers` partitions the cache by request-header values (e.g. `Accept-Encoding`) merged with the origin's `Vary` response; `Vary: *` anchors on URI to bound cardinality\n- **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\n- **Peak EWMA load balancing** - latency-aware backend selection alongside Round Robin, Consistent Hash, Random, Least Connections\n- **DashMap** - lock-free concurrent reads for ban list and route connections in the hot path\n- **Sub-0.5ms WAF evaluation** - precompiled regex patterns with zero overhead when disabled\n\n### :package: Reliability\n\n- **Worker process isolation** - fork+exec with socket passing via SCM_RIGHTS\n- **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::AllowProbe` for HalfOpen) so probe slots are allocated atomically across workers and a failure on one trips the breaker for every worker (WPAR-3)\n- **Health checks** - TCP and HTTP probes, backends marked degraded (\u003e2s) or down and removed from rotation\n- **Graceful drain** - per-backend active connection tracking with Closing/Closed lifecycle states\n- **Certificate hot-swap** - atomic swap via arc-swap, zero downtime during rotation\n- **Encrypted storage** - AES-256-GCM encryption for certificate private keys at rest\n\n## Quick Start\n\n### Install from .deb package\n\n```bash\n# Download the latest release\nwget https://github.com/Rwx-G/Lorica/releases/latest/download/lorica.deb\nsudo dpkg -i lorica.deb\n```\n\nThe package creates a `lorica` user, installs a systemd service (enabled by default), and starts Lorica on ports 8080 (HTTP), 8443 (HTTPS), and 9443 (dashboard).\n\nTo customize ports, workers, or log level, edit the systemd unit:\n\n```bash\nsudo systemctl edit lorica\n```\n\n```ini\n[Service]\nExecStart=\nExecStart=/usr/bin/lorica --data-dir /var/lib/lorica \\\n  --http-port 80 --https-port 443 --management-port 9443 \\\n  --workers 4 --log-level info\n```\n\n```bash\nsudo systemctl restart lorica\n```\n\n### Run with Docker\n\n```bash\ndocker build -t lorica .\ndocker run -p 8080:8080 -p 8443:8443 -p 9443:9443 \\\n  -v lorica-data:/var/lib/lorica lorica\n```\n\n### Run directly\n\n```bash\nlorica --data-dir /var/lib/lorica\n```\n\nOpen `https://localhost:9443` in your browser. On first run, a random admin password is printed to stdout.\n\n### CLI options\n\n```\nlorica [OPTIONS]\n\nOptions:\n  --data-dir \u003cPATH\u003e          Data directory (default: /var/lib/lorica)\n  --management-port \u003cPORT\u003e   Dashboard/API port (default: 9443)\n  --http-port \u003cPORT\u003e         HTTP proxy port (default: 8080)\n  --https-port \u003cPORT\u003e        HTTPS proxy port (default: 8443)\n  --workers \u003cN\u003e              Worker processes (default: 0 = single-process)\n  --log-level \u003cLEVEL\u003e        Log level (default: info)\n  --log-format \u003cFORMAT\u003e      Log format: json (default) or text\n  --log-file \u003cPATH\u003e          Log to file (in addition to stdout)\n  --version                  Print version\n```\n\n## Dashboard\n\nThe 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.\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"docs/screenshots/overview-guide.png\" alt=\"Overview - Getting Started Guide\" width=\"100%\"\u003e\n  \u003cbr\u003e\u003cem\u003eGetting started guide with interactive setup checklist\u003c/em\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"docs/screenshots/overview.png\" alt=\"Overview Dashboard\" width=\"100%\"\u003e\n  \u003cbr\u003e\u003cem\u003eOverview cockpit with system health, routes, security, and performance at a glance\u003c/em\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"docs/screenshots/routes.png\" alt=\"Routes Management\" width=\"100%\"\u003e\n  \u003cbr\u003e\u003cem\u003eRoutes table with hostname, backends, WAF mode, health status, and TLS\u003c/em\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"docs/screenshots/routesDrawer.png\" alt=\"Route Configuration Drawer\" width=\"100%\"\u003e\n  \u003cbr\u003e\u003cem\u003eRoute editor with 50+ settings across 7 tabs (General, Routing, Transform, Protection, Security, Cache, Upstream)\u003c/em\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"docs/screenshots/security.png\" alt=\"Security - WAF Rules\" width=\"100%\"\u003e\n  \u003cbr\u003e\u003cem\u003e49 WAF rules with per-rule toggle, covering SQLi, XSS, SSRF, Log4Shell, XXE, and more\u003c/em\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"docs/screenshots/system.png\" alt=\"System - Workers\" width=\"100%\"\u003e\n  \u003cbr\u003e\u003cem\u003eSystem page with worker health, heartbeat latency, CPU/memory gauges, and process metrics\u003c/em\u003e\n\u003c/p\u003e\n\n### Pages\n\n- **Overview** - cockpit dashboard with section helpers, setup checklist, system/route/security/performance cards\n- **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\n- **Backends** - manage backend addresses, weights, health check type (TCP/HTTP), TLS upstream, active connections\n- **Certificates** - upload PEM certificates, view expiry dates, provision via ACME/Let's Encrypt (HTTP-01, DNS-01)\n- **Security** - WAF event table with category filtering, 49 rule toggles, IP ban list with unban button\n- **SLA** - per-route passive/active SLA side-by-side, latency percentile tables, config editor, CSV/JSON export\n- **Load Tests** - test config management with clone, one-click execution, real-time SSE progress panel, historical results\n- **Active Probes** - CRUD for synthetic health probes with route selection, HTTP method/path/status/interval/timeout\n- **Access Logs** - scrollable real-time log stream via WebSocket with green pulsing indicator\n- **System** - worker table with PID, health, heartbeat latency; CPU/memory/disk gauges\n- **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\n- **Theme** - light/dark mode toggle\n\n## Architecture\n\nLorica is a Rust workspace with 28 crates: 16 forked from Cloudflare Pingora and 12 product crates. See [FORK.md](FORK.md) for the full fork lineage and renaming rules.\n\n| Crate | Purpose |\n|-------|---------|\n| `lorica` | CLI binary, supervisor, worker orchestration |\n| `lorica-proxy` | HTTP/HTTPS proxy engine (Pingora fork) |\n| `lorica-tls` | SNI certificate resolver, hot-swap, ACME |\n| `lorica-config` | SQLite store, migrations, TOML export/import |\n| `lorica-api` | axum REST API, auth, session management |\n| `lorica-dashboard` | Svelte 5 frontend embedded via rust-embed |\n| `lorica-waf` | WAF engine, OWASP rules, IP blocklist |\n| `lorica-notify` | Alert dispatch (stdout, SMTP, webhook) |\n| `lorica-bench` | SLA monitoring, load testing engine |\n| `lorica-worker` | fork+exec worker isolation, typed FD passing (Listener / Shmem / Rpc) |\n| `lorica-command` | Protobuf supervisor-worker command channel + pipelined RpcEndpoint (Envelope framing, in-flight demux, bounded backpressure), `Coalescer`, `GenerationGate` |\n| `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 |\n| `lorica-lb` | Load balancing (Round Robin, Peak EWMA, Hash, Random, Least Conn) |\n| `lorica-cache` | HTTP response cache, LRU eviction |\n| `lorica-limits` | Rate estimator + per-route `LocalBucket` / `AuthoritativeBucket` token-bucket primitives (lock-free CAS, 100 ms cross-worker sync) |\n\nData 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.\n\n## Performance\n\nMeasured on a single Linux VM (4 vCPU, 8 GB RAM):\n\n| Metric | Value |\n|--------|-------|\n| Single-process throughput | ~6,500 req/s |\n| Multi-worker throughput (4 workers) | ~25,000 req/s |\n| WAF evaluation latency | \u003c 0.5 ms per request |\n| WAF overhead on throughput | ~6% |\n| Dashboard bundle size | ~59 KB (gzipped) |\n| Config reload | Zero-downtime (arc-swap) |\n| Certificate hot-swap | Zero-downtime (atomic) |\n\n## Configuration Example\n\nCreate a route via the REST API:\n\n```bash\n# Authenticate\nTOKEN=$(curl -sk https://localhost:9443/api/v1/auth/login \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"password\":\"your-admin-password\"}' \\\n  -c - | grep session | awk '{print $NF}')\n\n# Create a backend\ncurl -sk https://localhost:9443/api/v1/backends \\\n  -b \"session=$TOKEN\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n    \"address\": \"127.0.0.1:8080\",\n    \"health_check_interval_s\": 10,\n    \"health_check_type\": \"http\",\n    \"health_check_path\": \"/healthz\"\n  }'\n\n# Create a route\ncurl -sk https://localhost:9443/api/v1/routes \\\n  -b \"session=$TOKEN\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n    \"hostname\": \"app.example.com\",\n    \"path_prefix\": \"/\",\n    \"backend_ids\": [1],\n    \"load_balancing\": \"peak_ewma\",\n    \"tls_enabled\": true,\n    \"certificate_id\": 1,\n    \"waf_enabled\": true,\n    \"waf_mode\": \"block\",\n    \"rate_limit_rps\": 100,\n    \"rate_limit_burst\": 50,\n    \"cache_enabled\": true,\n    \"cache_ttl_s\": 300,\n    \"force_https\": true,\n    \"security_headers\": \"strict\"\n  }'\n```\n\nOr just use the dashboard - it covers all the same operations with zero curl.\n\n## REST API Reference\n\nAll endpoints are served on the management port (default `9443`) over HTTPS. Protected endpoints require a session cookie obtained via `/api/v1/auth/login`.\n\n### Public endpoints\n\n| Method | Path | Description |\n|--------|------|-------------|\n| `POST` | `/api/v1/auth/login` | Authenticate (returns session cookie) |\n| `POST` | `/api/v1/auth/logout` | Invalidate session |\n| `GET` | `/metrics` | Prometheus metrics (no auth) |\n| `GET` | `/.well-known/acme-challenge/:token` | ACME HTTP-01 challenge response |\n\n### Routes\n\n| Method | Path | Description |\n|--------|------|-------------|\n| `GET` | `/api/v1/routes` | List all routes |\n| `POST` | `/api/v1/routes` | Create route |\n| `GET` | `/api/v1/routes/:id` | Get route |\n| `PUT` | `/api/v1/routes/:id` | Update route |\n| `DELETE` | `/api/v1/routes/:id` | Delete route |\n| `POST` | `/api/v1/validate/mtls-pem` | Parse a candidate client-CA PEM and return per-cert subjects |\n| `POST` | `/api/v1/validate/forward-auth` | Probe a candidate forward-auth URL (one GET, status + elapsed) |\n\n### Backends\n\n| Method | Path | Description |\n|--------|------|-------------|\n| `GET` | `/api/v1/backends` | List all backends |\n| `POST` | `/api/v1/backends` | Create backend |\n| `GET` | `/api/v1/backends/:id` | Get backend |\n| `PUT` | `/api/v1/backends/:id` | Update backend |\n| `DELETE` | `/api/v1/backends/:id` | Delete backend |\n\n### Certificates\n\n| Method | Path | Description |\n|--------|------|-------------|\n| `GET` | `/api/v1/certificates` | List certificates |\n| `POST` | `/api/v1/certificates` | Upload PEM certificate |\n| `POST` | `/api/v1/certificates/self-signed` | Generate self-signed certificate |\n| `GET` | `/api/v1/certificates/:id` | Get certificate |\n| `GET` | `/api/v1/certificates/:id/download?part={cert\\|key\\|chain\\|bundle}` | Download PEM material (rate-limited, audit-logged) |\n| `PUT` | `/api/v1/certificates/:id` | Update certificate |\n| `DELETE` | `/api/v1/certificates/:id` | Delete certificate |\n| `GET` | `/api/v1/cert-export/acls` | List per-pattern cert-export ACLs |\n| `POST` | `/api/v1/cert-export/acls` | Create a cert-export ACL rule |\n| `DELETE` | `/api/v1/cert-export/acls/:id` | Delete a cert-export ACL rule |\n| `POST` | `/api/v1/cert-export/reapply` | Re-export every certificate to disk |\n| `GET` | `/api/v1/cert-export/orphans` | List per-hostname subdirectories with no matching live cert |\n| `DELETE` | `/api/v1/cert-export/orphans/:name` | Remove one orphan subdirectory (sanitised + live-cert guard) |\n\n### ACME\n\n| Method | Path | Description |\n|--------|------|-------------|\n| `POST` | `/api/v1/acme/provision` | Provision via HTTP-01 |\n| `POST` | `/api/v1/acme/provision-dns` | Provision via DNS-01 |\n| `POST` | `/api/v1/acme/provision-dns-manual` | Start manual DNS-01 flow |\n| `POST` | `/api/v1/acme/provision-dns-manual/confirm` | Confirm manual DNS-01 |\n\n### WAF\n\n| Method | Path | Description |\n|--------|------|-------------|\n| `GET` | `/api/v1/waf/events` | Recent WAF events (with category filter) |\n| `DELETE` | `/api/v1/waf/events` | Clear WAF events |\n| `GET` | `/api/v1/waf/stats` | WAF statistics |\n| `GET` | `/api/v1/waf/rules` | List WAF rules |\n| `PUT` | `/api/v1/waf/rules/:id` | Enable/disable rule |\n| `GET` | `/api/v1/waf/rules/custom` | List custom rules |\n| `POST` | `/api/v1/waf/rules/custom` | Create custom rule |\n| `DELETE` | `/api/v1/waf/rules/custom/:id` | Delete custom rule |\n| `GET` | `/api/v1/waf/blocklist` | Blocklist status |\n| `PUT` | `/api/v1/waf/blocklist` | Enable/disable blocklist |\n| `POST` | `/api/v1/waf/blocklist/reload` | Reload blocklist |\n\n### SLA \u0026 Probes\n\n| Method | Path | Description |\n|--------|------|-------------|\n| `GET` | `/api/v1/sla/overview` | SLA overview for all routes |\n| `GET` | `/api/v1/sla/routes/:id` | SLA metrics for route |\n| `GET` | `/api/v1/sla/routes/:id/buckets` | Time-bucketed SLA data |\n| `GET` | `/api/v1/sla/routes/:id/config` | SLA config |\n| `PUT` | `/api/v1/sla/routes/:id/config` | Update SLA config |\n| `GET` | `/api/v1/sla/routes/:id/export` | Export SLA data (CSV/JSON) |\n| `GET` | `/api/v1/sla/routes/:id/active` | Active probe results |\n| `GET` | `/api/v1/probes` | List probes |\n| `POST` | `/api/v1/probes` | Create probe |\n| `GET` | `/api/v1/probes/route/:route_id` | Probes for route |\n| `PUT` | `/api/v1/probes/:id` | Update probe |\n| `DELETE` | `/api/v1/probes/:id` | Delete probe |\n\n### Load Testing\n\n| Method | Path | Description |\n|--------|------|-------------|\n| `GET` | `/api/v1/loadtest/configs` | List configs |\n| `POST` | `/api/v1/loadtest/configs` | Create config |\n| `PUT` | `/api/v1/loadtest/configs/:id` | Update config |\n| `DELETE` | `/api/v1/loadtest/configs/:id` | Delete config |\n| `POST` | `/api/v1/loadtest/configs/:id/clone` | Clone config |\n| `POST` | `/api/v1/loadtest/start/:config_id` | Start test (requires confirm) |\n| `POST` | `/api/v1/loadtest/start/:config_id/confirm` | Confirm and execute |\n| `GET` | `/api/v1/loadtest/status` | Current test status |\n| `GET` | `/api/v1/loadtest/ws` | WebSocket real-time progress |\n| `POST` | `/api/v1/loadtest/abort` | Abort running test |\n| `GET` | `/api/v1/loadtest/results/:config_id` | Test results |\n| `GET` | `/api/v1/loadtest/results/:config_id/compare` | Compare runs |\n\n### Cache \u0026 Bans\n\n| Method | Path | Description |\n|--------|------|-------------|\n| `DELETE` | `/api/v1/cache/routes/:id` | Purge route cache |\n| `GET` | `/api/v1/cache/stats` | Cache hit/miss stats |\n| `GET` | `/api/v1/bans` | List banned IPs |\n| `DELETE` | `/api/v1/bans/:ip` | Unban IP |\n\n### System \u0026 Configuration\n\n| Method | Path | Description |\n|--------|------|-------------|\n| `PUT` | `/api/v1/auth/password` | Change password |\n| `GET` | `/api/v1/settings` | Global settings |\n| `PUT` | `/api/v1/settings` | Update settings |\n| `GET` | `/api/v1/status` | System status summary |\n| `GET` | `/api/v1/system` | CPU, memory, disk usage |\n| `GET` | `/api/v1/workers` | Worker heartbeat metrics |\n| `GET` | `/api/v1/logs` | Access logs |\n| `DELETE` | `/api/v1/logs` | Clear logs |\n| `GET` | `/api/v1/logs/ws` | WebSocket log stream |\n| `POST` | `/api/v1/config/export` | Export config as TOML |\n| `POST` | `/api/v1/config/import` | Import TOML config |\n| `POST` | `/api/v1/config/import/preview` | Preview import diff |\n| `GET` | `/api/v1/notifications` | List notification configs |\n| `POST` | `/api/v1/notifications` | Create notification config |\n| `PUT` | `/api/v1/notifications/:id` | Update notification config |\n| `DELETE` | `/api/v1/notifications/:id` | Delete notification config |\n| `POST` | `/api/v1/notifications/:id/test` | Test notification channel |\n| `GET` | `/api/v1/preferences` | List user preferences |\n| `PUT` | `/api/v1/preferences/:id` | Update preference |\n| `DELETE` | `/api/v1/preferences/:id` | Delete preference |\n\n## Building from Source\n\n```bash\n# Prerequisites\n# - Rust 1.88+\n# - Node.js 20+ (Vite 8 minimum, dashboard compilation)\n# - Linux (x86_64)\n\ngit clone https://github.com/Rwx-G/Lorica.git\ncd Lorica\ncargo build --release\n\n# Binary is at target/release/lorica\n# The Svelte frontend is compiled automatically during cargo build.\n```\n\n### Running tests\n\n```bash\n# All Rust unit tests (~2091 tests across 28 crates)\ncargo test --workspace\n\n# Product crate tests only (~1306 tests - lorica-native, include\n# the v1.5.0 hardening coverage : rate-limit buckets, body-size\n# layers, session rotation, public_version masking, ammonia bypass\n# corpus, map_err context preservation, cert-export orphan sweep)\ncargo test -p lorica-config -p lorica-api -p lorica -p lorica-waf \\\n           -p lorica-notify -p lorica-bench -p lorica-worker \\\n           -p lorica-command -p lorica-limits -p lorica-shmem \\\n           -p lorica-challenge -p lorica-geoip\n\n# Pingora-forked crate tests (~690 tests)\ncargo test -p lorica-core -p lorica-proxy -p lorica-http \\\n           -p lorica-error -p lorica-tls -p lorica-cache \\\n           -p lorica-pool -p lorica-runtime -p lorica-timeout \\\n           --features ring -p lorica-lb\n\n# End-to-end tests driving a real Pingora Server (95 tests, 16 binaries)\ncargo test -p lorica --test bot_rpc_cache_e2e_test \\\n                     --test canary_e2e_test \\\n                     --test config_reload_rpc_e2e_test \\\n                     --test connection_filter_test \\\n                     --test forward_auth_e2e_test \\\n                     --test header_routing_e2e_test \\\n                     --test metrics_pull_rpc_e2e_test \\\n                     --test mirror_e2e_test \\\n                     --test mtls_e2e_test \\\n                     --test proxy_config_test \\\n                     --test proxy_routing_test \\\n                     --test rate_limit_e2e_test \\\n                     --test rate_limit_sync_e2e_test \\\n                     --test response_rewrite_e2e_test \\\n                     --test swr_e2e_test \\\n                     --test verdict_breaker_rpc_e2e_test\n\n# Frontend tests (320 Vitest tests across 9 files)\ncd lorica-dashboard/frontend \u0026\u0026 npx vitest run\n```\n\n#### Test coverage by layer\n\n| Layer | Count | Notes |\n|---|---|---|\n| 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. |\n| 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 |\n| 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) |\n| Frontend (vitest / svelte-check) | 320 | Form validation, type safety, component wiring |\n| **Total shipping tests** | **~2411** | |\n\n#### Docker end-to-end suites\n\n`tests-e2e-docker/` spins Lorica up against real backend containers\nand drives 660+ assertions through the actual network stack:\n\n```bash\ncd tests-e2e-docker\n./run.sh                                    # single-process (429 asserts) + workers mode (109) + cert-export (26)\ndocker compose --profile bot run --rm bot-smoke                   # 29 asserts - graded bot challenge\ndocker compose --profile bot-workers run --rm bot-smoke-workers   # 29 asserts - same under --workers 2\ndocker compose --profile geoip run --rm geoip-smoke               # 16 asserts - country allow/deny\ndocker compose --profile rdns run --rm rdns-smoke                 # 7  asserts - forward-confirmed rDNS bypass\ndocker compose --profile otel run --rm otel-smoke                 # 16 asserts - OTLP + W3C + log/trace correlation\ndocker compose --profile otel-workers run --rm otel-smoke-workers # 16 asserts - same under --workers 2\ndocker compose --profile cert-export run --rm cert-export-smoke   # 26 asserts - PEM disk export + ACL + reapply\n```\n\nThe two intentional gaps in the Docker harness are:\n\n- **mTLS client-cert handshake** (495 / 496 / 200 based on the\n  presented cert). The e2e container starts without any TLS certs\n  pre-loaded so the HTTPS listener on port 8443 is never built -\n  driving `curl --cert` needs a staged environment. The config\n  surface (CA PEM validation, `required` + `allowed_organizations`\n  hot-reload) is covered.\n- **Connection pre-filter TCP drop** (scanner IP refused at\n  `accept()` before TLS). The test-runner sits on the same Docker\n  network as Lorica, so any CIDR that would cover a real scanner\n  also covers the runner - asserting the drop from inside would be\n  self-blocking. The config round-trip (valid CIDR accepted, garbage\n  rejected 400) is covered.\n\nValidate both manually on staging when touching the surrounding\ncode paths.\n\n## systemd Service\n\nThe `.deb` and `.rpm` packages install a hardened systemd unit with:\n\n- `ProtectSystem=strict`, `PrivateTmp=yes`, `NoNewPrivileges=yes`\n- `MemoryDenyWriteExecute=yes`, `SystemCallFilter=@system-service`\n- `RestrictNamespaces=yes`, `RestrictSUIDSGID=yes`\n- Runs as dedicated `lorica` user with `CAP_NET_BIND_SERVICE`\n- Service auto-starts on install and auto-restarts on upgrade\n- Data directory (`/var/lib/lorica`) preserved across upgrades\n\nCustomize the service (e.g. enable workers) via drop-in override:\n\n```bash\nsudo systemctl edit lorica\n```\n\n```ini\n[Service]\nExecStart=\nExecStart=/usr/bin/lorica --workers 6\n```\n\n## Performance Tuning\n\nSee [docs/tuning.md](docs/tuning.md) for kernel parameters (`sysctl`), file descriptor limits, worker configuration, cache settings, and a production readiness checklist. Run [bench/](bench/) for reproducible throughput measurements.\n\n## Worker Mode\n\nWhen running with `--workers N \u003e= 1`, see [docs/worker-mode.md](docs/worker-mode.md) for the operational notes (which settings require a supervisor restart, what changes between single-process and worker mode).\n\n## Package Verification\n\nRelease `.deb` and `.rpm` packages are GPG-signed. Import the public key to verify:\n\n```bash\ncurl -fsSL https://github.com/Rwx-G/Lorica/raw/main/docs/lorica-signing-key.asc | sudo gpg --dearmor -o /usr/share/keyrings/lorica.gpg\ngpg --verify lorica.deb.asc lorica.deb\n```\n\n## Roadmap\n\n| Version | Features | Status |\n|---------|----------|--------|\n| v1.4.0 | OpenTelemetry tracing (OTLP), GeoIP country blocking, Bot protection (PoW / captcha / cookie with 5-category bypass matrix) | Shipped |\n| 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 |\n| **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 |\n| 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 |\n| v2.0.0 | HTTP/3 (QUIC), TCP/L4 proxying | Planned |\n\n### Backlog (tracking only, blocked on upstream)\n\n- **`rustls-pemfile` removal in the `lorica-tls` fork.** RUSTSEC-2025-0134 (unmaintained) still shows transitively through our Pingora fork. Native Lorica code migrated to `rustls-pki-types` in v1.5.0 ; the transitive dep clears once Pingora upstream migrates.\n- **`rand 0.8` removal in forked crates.** Native Lorica code bumped to `rand 0.9` in v1.5.0. RUSTSEC-2026-0097 (unsound with custom logger) was cleared in v1.5.8 by bumping the transitive `0.8` line to `0.8.6` ; removing the `0.8` line entirely still depends on the upstream forks (`lorica-runtime`, `lorica-limits`) and the `axum` / `tungstenite` majors. Same monitoring as the `rustls-pemfile` row.\n\nThe table above tracks feature milestones; the current release and the patch cycles (v1.5.3+) live in [CHANGELOG.md](CHANGELOG.md).\n\nSee [COMPARISON.md](COMPARISON.md) for a detailed feature comparison with Nginx, Traefik, HAProxy, Caddy, BunkerWeb, Sozu, and Pingora.\n\n## Not Supported\n\n| Feature | Status | Rationale |\n|---------|--------|-----------|\n| **HTTP/3 / QUIC** | Planned | Waiting for [Pingora PR #524](https://github.com/cloudflare/pingora/pull/524) (tokio-quiche integration) to merge upstream |\n| **io_uring** | Not planned | tokio-uring is unmaintained since 2022. epoll via Tokio delivers sufficient performance (40M req/s at Cloudflare scale) |\n| **Windows / macOS** | Not supported | Linux x86_64 only (fork+exec worker model requires Linux) |\n| **OpenSSL / BoringSSL** | Removed | rustls is the sole TLS provider |\n\n## License\n\nApache-2.0 - see [LICENSE](LICENSE).\n\n## Credits\n\nBuilt on [Pingora](https://github.com/cloudflare/pingora) by Cloudflare (Apache-2.0). See [NOTICE](NOTICE) and [FORK.md](FORK.md) for fork details.\n\nAuthor: Rwx-G\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frwx-g%2Florica","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frwx-g%2Florica","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frwx-g%2Florica/lists"}