eggrd
Sheet 1 of 1  ·  Section through the request path  ·  Apache-2.0

The front door
your app shipped
without.

One static binary in front of your app. It owns the way in: TLS, authentication, rate limiting, input rules. It owns the way out too: redaction, hardened response headers, and hard ceilings on what your LLM calls may spend.

Nothing it refuses ever reaches your app. And nothing about your app changes to get that: no agent, no SDK, no library call.

△ 0.7.1 · SECURITY RELEASE A WAF Host allow-list rule now sees the host of every request: one sent over cleartext HTTP/2, one in an absolute-form target, and one with no Host at all, which 0.7.0 let past it. Credentials written into an upstream URL no longer reach the logs. And there are security guides: a hardening checklist, logging and evidence, and an incident-response runbook. Security guides  ·  Upgrade notes

△ NEW IN 0.7 Mutual TLS on every hop, upstream pools with health checks and failover, the client's address from an L4 balancer, API usage by route, consumer and upstream, and a connection gate in front of everything. What 0.7 adds  ·  Install 0.7.1  ·  Changelog

Project
eggrd
Drawn as
Section A–A
Licence
Apache-2.0
Revision
v0.7.1
Read the source Documentation

What each chamber does

Schedules · both directions
Request path schedule — inbound
StageConfigBehaviour
Connection[server]Every connection has header_read_timeout (30 s) from accept to a complete request head, a PROXY header included, so a slowloris client cannot hold a socket; max_connections caps the rest at accept. Behind an L4 balancer, proxy_protocol reads the client's address, believed only from proxy_protocol_trusted; the admin port reads it too, so an NLB can health-check /__edgeguard/ready over HTTP (how). Behind an L7 balancer, trust_forwarded_for takes the address the balancer appended to X-Forwarded-For (forwarded_hops from the right), never the client's own. On SIGTERM readiness turns 503 and in-flight requests drain; behind an NLB, set drain_delay to 20–30 s so it outlasts the NLB's deregistration lag, and terminationGracePeriodSeconds to cover drain_delay + drain_timeout. △ 0.7
TLS[tls]Terminates TLS ✓ PROVEN: TLS 1.3, the certificate verified, plaintext refused on the TLS port, every hardening header applied through the tunnel. Three ways to hold a certificate: bring one, let [tls.acme] issue it ✓, or set self_signed = true and one is generated at first boot ✓, so TLS never fails to start for want of a file. ACME wins when both are configured.
HTTP → HTTPS[tls] redirect_portA plaintext listener answering 308, not 301, so a POST is replayed over TLS instead of being silently downgraded to a GET ✓. A forged Host gets 400 rather than a reflected redirect, and the ACME challenge path is never redirected.
Client certificate[tls.client_auth]Mutual TLS: a client without a certificate from your CA cannot finish the handshake, CRLs reload without a restart, and session resumption is off so a revoked certificate cannot ride an old session back in. Behind a balancer that does the mTLS, [trusted_client_cert_header] reads it instead, trusted by the balancer's address or by its own client certificate (trusted_proxy_names). △ 0.7
Authentication[auth]Basic, API key, JWT or the client certificate (mode = "mtls"), validated before the request reaches your app. Asserted, not assumed. A credential header sent twice is 401, so the app can never be handed a different copy from the one the edge checked. △ 0.7
Rate limit[ratelimit]Per-IP and per-key ceilings. store = "redis" shares one ceiling across replicas ✓
Input rules[waf]WAF-lite inspection. Off by default — you turn it on deliberately.
IP access[access]allow and deny lists evaluated before anything downstream.
Upstream[[upstreams]]A path prefix goes to one upstream or a pool: priority, weighted round robin, least connections or consistent hashing (client IP, path, a header, a cookie or a query parameter), sticky cookies, a backup tier, health checks (on a separate management port, scheme or address, see below) and failover on connect errors. https:// upstreams are verified, with mutual TLS through [upstream_tls]. △ 0.7
Response path schedule — outbound
StageConfigBehaviour
Headers[headers]CSP, HSTS and cookie hardening applied to what your app returns, plus Server and X-Powered-By stripped. Every header, listed.
Redaction[llm.dlp]Reversible masking on the LLM lane: the provider sees a placeholder, the caller gets their own values back. Detail B below.
Access log[log] queryQuery values are redacted by name and by shape, so ?api_key= and a bare JWT both log as <redacted> while page numbers and slugs stay readable. On by default since 0.4.0 ✓. From 0.7 each line also says who called, which route, which upstream, its time and the bytes moved (schema: 2) △ 0.7.
Compression[validation]compress_responses — negotiated response compression.
CORS preflight[cors]Preflight answered at the edge, with the allow-list held in config.

Revision 0.7

Release 0.7.1 · tested, not yet run live
ItemStateWhat that means
Mutual TLS, inbound △ TESTED Client certificates on the TLS listener against your CA, with CRLs that reload on change. auth.mode = "mtls" makes the certificate the credential: its SPIFFE ID, DNS name, email or CN is the principal that limits and budgets key on. TLS session resumption is off while it is on, because a resumed session is not verified again; the tests revoke a certificate and fail with resumption back on. Behind a balancer that does the mTLS, its forwarded certificate header can be trusted by the balancer's own client certificate (trusted_proxy_names), not only by its address.
TLS and mTLS to upstreams △ TESTED An https:// upstream used to load and then answer every request 502. It is now verified against the public roots, or only your CA with [upstream_tls] ca_path, and can present a client certificate that reloads when it changes on disk. There is no option to skip verification.
Private PKI to the control plane △ TESTED The control plane, log shipper, tracing and OTLP exporters, the JWKS fetch and alerts each take ca_path, a client certificate and an explicit proxy, so a corporate CA and an egress proxy no longer stop them connecting. [outbound] sets the proxy, the client certificate and extra_ca_path once for all of them; a CA there is added to the public roots, never a replacement. ACME issuance and renewal go through the same egress proxy. A last-known-good policy is kept on disk across restarts (control_plane.cache_path).
Upstream pools △ TESTED Priority, weighted round robin, least connections or rendezvous hashing (by client IP, path, header, cookie or query:<name>), with sticky cookies (a Max-Age, stripped before the app if you like), a backup tier, active HTTP or TCP health checks (expect_status, expect_body or expect_body_regex, a method and headers, a separate management port), ejection on connect errors or repeated 5xx, and failover for any request that reached nothing. GET /__edgeguard/upstreams says why a target is out. Checks can also switch scheme (health_check.scheme) and dial a per-target check_host.
PROXY protocol △ TESTED v1 and v2 from an L4 balancer, believed only from proxy_protocol_trusted; from anyone else a connection that starts with one is closed, because it would be a client choosing its own address. The admin port reads it too, as optional, so an NLB's HTTP health check of /__edgeguard/ready works there, and the PROXY header and the request head share one header_read_timeout rather than getting one each.
API-usage monitoring △ TESTED Per-route and per-consumer series, per-upstream results and latency, access records with schema: 2, and monitoring bundle 1.6.2: an API-usage dashboard and alerts with promtool tests. Also edgeguard_requests_in_flight and edgeguard_tls_handshakes_total per listener, and edgeguard_tls_no_client_hello_total for the health checks that used to count as failed handshakes. Route labels come from the config, never the request path, so the series count is bounded.
Repeated credential header △ TESTED A security fix. The auth gate read the first Authorization or API-key header, but the request went upstream with the last, so a valid key followed by a forged token passed the edge and reached the app as the forged one. A credential header sent twice is now 401 and never forwarded. Every other repeated header now reaches the app with all its values in order, and HTTP/2 Cookie headers are joined into one.
Client address behind an L7 balancer △ TESTED A security fix. Up to 0.5.0, with trust_forwarded_for, the client was the first X-Forwarded-For entry, which the client writes itself, so any client could pick its address through the balancer: a fresh rate-limit bucket per request, a way past [access] rules, a forged address in the logs. The client is now the entry server.forwarded_hops from the right (default 1; 2 for a GCP external Application Load Balancer or CloudFront in front of an ALB; per balancer), the one the trusted proxy appended, counted in edgeguard_forwarded_for_total{result}. X-Forwarded-Proto is read from its last value. The app receives eggrd's own X-Forwarded-For, X-Real-IP, Forwarded and X-Forwarded-Host, never the client's, and no True-Client-IP.
Connection gate △ TESTED One accept loop for the public, redirect and admin listeners, with header_read_timeout, max_connections and a graceful drain: before 0.7 a connection sending a byte at a time was held open indefinitely, and HTTPS requests were cut on every restart.

These rows are red because each is proven by tests that fail without it, against rustls endpoints, stub control planes and OpenSSL-made certificates, rather than by a run against live infrastructure. A row turns green once it has been run the way the rows in the revision block below were.

# mutual TLS: the certificate is the credential
[tls]
enabled   = true
cert_path = "/etc/eggrd/server.pem"
key_path  = "/etc/eggrd/server.key"

[tls.client_auth]
mode      = "required"
ca_path   = "/etc/eggrd/client-ca.pem"
crl_paths = ["/etc/eggrd/client-ca.crl"]
forward_client_cert = "xfcc"

[auth]
mode = "mtls"
[auth.mtls]
allowed = ["spiffe://prod.example/ns/web/*"]
# a pool: two servers, a standby behind them
[[upstreams]]
path    = "/"
name    = "app"
targets = [{ url = "http://10.0.1.5:8080", weight = 2 },
           "http://10.0.1.6:8080"]
balance = "least_conn"
backup  = ["https://standby.example.com"]

[upstreams.health_check]
path          = "/healthz"
expect_status = "2xx"

[server]
header_read_timeout = "30s"
max_connections     = 10000

# every client the edge opens itself, set once
[outbound]
proxy         = "http://egress.internal:3128"
extra_ca_path = "/etc/eggrd/corp-ca.pem"

△ REVISION 0.7 behaviour that changes on upgrade. A request that sends its credential header (Authorization, or the API-key header) more than once is now 401; under mode = "none" or "mtls", which read no header, duplicates are forwarded for the app to judge, and every repeated header now arrives with all its values rather than only the last. Connecting to an upstream now gives up after 5 s with a 502 rather than waiting out upstream_timeout, which is what lets a pool move on in time; validation.upstream_connect_timeout = "0" restores the old wait. Keep-alive connections idle for 30 s are closed, by the same header_read_timeout. A streamed response's access line is written when its last byte is sent, so its size and duration are what was actually sent. Every key is in the configuration reference.

△ NEW IN 0.7.0 a pool's health check can use another scheme (health_check.scheme), for a TLS app whose management port speaks plain HTTP or the reverse, and each target can be checked at another address (check_host), such as its management network, while requests still go to its url. The check still sends the target's own name as Host, and an https:// check of an https:// target verifies the target's own name.

# a TLS app, checked on a plain-HTTP management port on another network
[[upstreams]]
path    = "/"
targets = [{ url = "https://app-1.internal:8443", check_host = "10.0.9.5" },
           { url = "https://app-2.internal:8443", check_host = "10.0.9.6" }]

[upstreams.health_check]
path   = "/healthz"
port   = 9443
scheme = "http"          # "" (default) keeps the target's own scheme

△ UPGRADING FROM 0.5 With trust_forwarded_for, the client is now the X-Forwarded-For entry forwarded_hops from the right, not the first. Set it from what your balancer appends: 1 for an AWS ALB (default append mode), Cloudflare, Azure Application Gateway, nginx or Traefik; 2 for a GCP external Application Load Balancer, which appends two entries, or CloudFront in front of an ALB. Too low makes every client the balancer; anything else, check before use (how). The header is still believed from every peer, so let only the balancer reach eggrd's traffic port, and in a chain every proxy but the outermost only from the one in front. eggrd also now sets X-Real-IP and Forwarded to the resolved client and X-Forwarded-Host to the received Host, and removes True-Client-IP. NLB health checks, rolling restarts and client IP preservation are in the operations notes.

The request that never reached your app

Detail D · evaluation order

A gate that forwards the request and then returns the right status has failed at its job twice over: your app did the work, and on the LLM lane the provider was already paid. What matters is not the status code a client sees but whether anything behind eggrd was ever asked.

Until 0.4.0 that was an assertion in a comment. It is now a test. A counting stub stands in for your app, and every denial path asserts it saw exactly zero requests, with a control asserting an admitted request reaches it exactly once, so a counter that never incremented could not make the rest pass vacuously.

The five gates are drawn in evaluation order, and the last column of the schedule below is the one that matters.

The drawing is set to the 401 case. Every other refusal is in the schedule below.

Stopped at Auth Status 401 outcome unauthorized Your app saw 0 requests

Refusal schedule — every one of these is a test with a counting stub behind it
Refused atStatusoutcomeReached your app
IP access403ip_deniedno
Rate limit429rate_limitedno
Rate limit · store down503limiter_errorno, it fails closed rather than open
Auth401unauthorizedno
Body cap413payload_too_largeno
WAF403forbiddenno
Nothing — allowed200okyes, exactly once

The LLM lane carries the same guarantee and the same kind of test: over budget, an off-allow-list model, a DLP block and a budget-store outage each assert the provider was called zero times. Confirmed to have teeth by mutation: making a denial path call the provider before returning makes the test fail while the client still receives the identical status the old test accepted. Every code above is listed with its trigger in the API reference.

Three details, drawn larger

Callouts A · B · C
A

Shared-store rate limit

A per-process counter stops meaning anything the moment you run two replicas. eggrd can hold the limit in a store both replicas read, so the ceiling is the ceiling rather than the ceiling times your replica count.

[ratelimit] store = "redis" ✓ PROVEN

B

Reversible redaction

An irreversible [REDACTED] protects the provider's view and destroys yours. On the LLM lane eggrd keeps a per-request mask map: the provider sees a placeholder, and the response is unmasked back to that caller's own values — buffered or streamed.

[llm.dlp] mode = "redact" · reversible = true

C

Budgets that hold

A ceiling checked after the fact is a report, not a limit. eggrd reserves against the budget before the provider call and reconciles after, so a burst of concurrent requests cannot walk past the number together.

[[llm.budgets]] · fail-closed

The metered line

Detail C · LLM traffic

The same binary sitting in front of your app can sit in front of your model provider. It parses OpenAI-compatible traffic, counts input, output, cached and reasoning tokens separately, then prices them against a book you supply.

Metering is observe-only until you say otherwise: with no price book and no budgets it counts and reports, and changes nothing about the request. Turn on a budget and the line becomes a ceiling, enforced before the call rather than after the invoice.

An unknown model is a decision, not a default. on_unpriced_model either counts it and flags it, or rejects the request outright, so a model you never priced cannot quietly bill at zero.

# edgeguard.toml
[llm]
enabled = true
on_unpriced_model = "block"

[llm.models."gpt-4o"]
input_per_1m  = 2.5
output_per_1m = 10.0

[[llm.budgets]]
name  = "team-platform"
scope = "key"
unit  = "usd"
limit = 250.0
window = "30d"

[alerts]
budget_consumed_threshold = 0.8

Revision block

Seven run · one not deployed
ItemStateWhat that means
Request gates, in order ✓ PROVEN Auth, rate limiting, the WAF, IP access and the body cap are each asserted to reject before the upstream is contacted, not merely to return the right status: a counting stub stands in for the app and every denial path asserts it saw zero requests, with a control asserting an admitted one reaches it exactly once. Added in 0.4.0, and confirmed by mutation: making the auth path forward before rejecting fails the test while the client still sees the same 401.
Access-log query redaction ✓ PROVEN A request carrying ?token=…, ?api_key=… or a bare JWT logs as <redacted>, on by default, while the same request reaches the upstream, the WAF, rate limiting and DLP unredacted. One of the integration tests puts an SQL-injection payload in a parameter named code, which is on the redaction list, so the two behaviours are proved not to interfere.
TLS termination ✓ PROVEN TLS 1.3 negotiated, the certificate verified, the upstream response proxied back through it, plaintext refused on the TLS port, and all six hardening headers present through the tunnel.
Self-signed certificates ✓ PROVEN self_signed = true generated a certificate on first boot, rustls loaded it, curl --cacert got 200 through it and an untrusting client was refused — so it is real TLS, not a bypass. The key file's mode is asserted 0600. What it does not do is prove identity: right for localhost, a private network or staging, wrong for a public domain, where [tls.acme] is the answer and wins when both are configured.
HTTP → HTTPS redirect ✓ PROVEN Plaintext answered 308 to the TLS port with path and query intact; a POST followed the redirect and arrived upstream still a POST, with its body; a forged Host outside redirect_hosts got 400 and no Location; and /.well-known/acme-challenge/ got 404 rather than a redirect, because bouncing the token to the port whose certificate is being issued would deadlock the order.
Shared-store rate limiting ✓ PROVEN Exercised across two replicas sharing one Redis. Thirty requests from one client, alternating between replicas, were allowed 5 times, the configured burst, enforced once globally against a single key. The same run with store = "local" allowed 10, which is the documented per-replica behaviour and the reason the shared store exists.
ACME certificate issuance ✓ PROVEN, AND IT WAS BROKEN Not “untested and probably fine”: a two-year-old client could no longer parse the CA's authorization payload. Found by running it, fixed, and it now issues a real Let's Encrypt certificate in about five seconds, as CN=acme-test.eggrd.dev from a domain we control, and passes against Pebble locally. How to re-run it, and everything that was wrong.
WASM edge worker △ RUNS · NOT DEPLOYED Builds a deployable bundle and serves requests on workerd, the runtime Cloudflare runs in production: no credentials and a wrong password each returned 401, the correct one returned 200 from the origin carrying all six hardening headers, with Server and X-Powered-By stripped. It has not been deployed to a Cloudflare account, so routes, custom domains and secret bindings remain untested. This row stays red until that has been run.

Drawings carry a revision block because the honest state of a design is part of the design, and a row goes green here only after something has been run. Checking them is what changed them. The rate limiter passed. ACME turned out to be broken rather than merely untested. The WASM row once claimed more than its build could deliver and stayed marked unproven for as long as that was true; it now runs, and it is still not deployed, which is why it is the one red mark on this sheet. The runs are written down in docs/ so you can repeat them.

△ REVISION 0.4.0 one behaviour changes on upgrade. [log] query now defaults to redact, so a dashboard keyed on a ?campaign= value or a SIEM rule matching ?session= will find <redacted> there after upgrading. Set [log] query = "full" to keep the previous verbatim behaviour. Nothing else about the request, the routing or the upstream changes: only the log line is affected, and the upstream still receives the client's target byte for byte. The other two 0.4.0 additions, self-signed certificates and the redirect listener, stay off until you configure them.

Put it in front of something

Setup · four stacks
# install: 0.7.1, the current release
cargo install eggrd

# scaffold a starter config beside your app, then check it before it sees traffic
edgeguard init
edgeguard doctor

The crate is eggrd; the binary it installs is still named edgeguard, an earlier working title kept so existing deployments keep working. init writes an annotated edgeguard.toml and a Dockerfile rather than a blank file, and refuses to clobber either. doctor then lints that config and walks the boot path, so a bad setting fails at your terminal instead of in front of traffic.

# or skip the toolchain entirely
docker run -p 8080:8080 \
  -v ./edgeguard.toml:/etc/edgeguard/edgeguard.toml \
  mancube/eggrd:0.7.1              # or mancube/eggrd:latest

A static musl binary on distroless: no shell and no package manager inside it to attack. linux/amd64 and linux/arm64, each built on its own native runner. 0.4.0 more than halved the binary, from 16.6 MiB to 7.5 MiB, by dropping aws-lc-sys, which nothing used: it rode in on feature unification, and it was the build's only C dependency, the piece most likely to break a musl or cross target.

There are two shapes, and init picks the right one for what it finds. Either eggrd becomes your container's entrypoint and runs your app as a child on APP_PORT, or it runs as its own service pointed at an upstream URL. Neither needs a line of your application code.

Pick the stack you are on. Every command below is from the example files in the repository, not an illustration of them.

Node

# in your app's repo
edgeguard init                    # detects Node, writes the config + a Dockerfile
echo -n 'your-password' | edgeguard --hash

# eggrd takes the public port; your app moves to APP_PORT
PORT=8080 APP_PORT=3000 edgeguard --config edgeguard.toml \
  --wrap "node server.js"

Your app keeps reading its port from the environment: eggrd sets PORT=$APP_PORT for the child process, so server.js needs no edit. What changes is which process owns the public port.

The Dockerfile.edgeguard that init writes copies the binary out of the published image and makes it the entrypoint, so the deployed container has the same front door as your laptop.

Python

# in your app's repo
edgeguard init                    # detects Python
edgeguard doctor

PORT=8080 APP_PORT=3000 edgeguard --config edgeguard.toml \
  --wrap "uvicorn app.main:app --host 127.0.0.1 --port $APP_PORT"

The wrap command runs through sh -c, so $APP_PORT expands inside it. Bind the app to 127.0.0.1: once eggrd owns the public port there is no reason for the app to be reachable on its own.

Gunicorn, Django and Flask take the same shape. Only the command inside --wrap changes.

Docker Compose

services:
  edge:
    image: mancube/eggrd:0.7.1
    ports: ["8080:8080"]
    environment:
      UPSTREAM: http://app:3000
      EDGEGUARD_JWT_SECRET_FILE: /run/secrets/jwt_secret
    command: ["--config", "/etc/edgeguard/edgeguard.toml"]
    volumes:
      - ./edgeguard.toml:/etc/edgeguard/edgeguard.toml:ro
    secrets: [jwt_secret]

  app:
    image: ghcr.io/example/your-app:latest
    expose: ["3000"]          # internal only, never published

secrets:
  jwt_secret:
    file: ./secrets/jwt_secret.txt

The app is exposed rather than ports-published, so it is reachable only through eggrd. That is the whole point of a front door, and it is a property of the compose file rather than of any code you wrote.

The JWT secret arrives as a file. Every secret variable has a *_FILE twin, the convention Docker and Kubernetes mounts already use, so the value never lands in the config file or in a ps-visible environment. A mount that cannot be read is a hard startup error rather than a proxy quietly running with no secret.

Kubernetes

env:
  - name: UPSTREAM
    value: http://app.default.svc:3000
  - name: ADMIN_PORT              # ops endpoints, off the public port
    value: "9090"
  - name: EDGEGUARD_JWT_SECRET_FILE
    value: /run/secrets/eggrd/jwt
volumeMounts:
  - name: eggrd-secrets
    mountPath: /run/secrets/eggrd
    readOnly: true

livenessProbe:
  httpGet: { path: /__edgeguard/health, port: 9090 }
readinessProbe:
  httpGet: { path: /__edgeguard/ready, port: 9090 }

ADMIN_PORT moves health, readiness and metrics onto a private listener, so the ops trio is not answering on the port the internet reaches. Keep it on the pod network; it carries no authentication by design.

The two probes answer different questions on purpose. Liveness consults nothing, so a restarting upstream never kills the proxy in front of it. Readiness reflects the upstream, so a pod whose app is down leaves the Service instead of accepting traffic it cannot serve.

Somewhere else? Fly.io, Render, Railway and a hardened systemd unit each have a shipped example file in examples/, and every environment variable they set is listed in the CLI reference. Static and edge hosts, where no long-lived proxy can run, are the one shape this does not fit: use edgeguard generate to emit the hardening config, or the Cloudflare Worker.

One proxy, or a fleet of them

Editions · Community · Enterprise

Everything on this sheet so far is the Community edition: the proxy, Apache-2.0, nothing withheld. One proxy in front of one app needs nothing else, and that will not change — a capability that matters to a single proxy belongs in the free binary, not behind a licence.

eggrd Enterprise is the layer above a fleet of those proxies: a control plane you run inside your own network, air-gapped included. Your edges pull their policy from it and report back to it — version, liveness, policy drift and the certificate each one is serving.

The licence key is checked offline against a key built into the control plane; nothing calls home. A lapsed licence turns the control plane read-only and never stops an edge, which keeps serving on its last policy. The table lists only what ships today.

Edition schedule
CapabilityCommunityEnterprise
LicenceApache-2.0Annual agreement, offline key
The proxy itselfEverything aboveThe same binary on every edge
Central policy—Versioned; edges pull and hot-reload it
Fleet view—Version, liveness, drift, served certificate
CertificatesRotation on each edgeInventory and expiry alerts across edges
ACME rate limitsPer-edge budgetCoordinated across the fleet
Usage and quotasPer-edge metricsAcross edges, enforced at the edge
Operators—OIDC sign-in, per-app roles, audit log with export
AlertsPrometheus rulesRouted to webhooks (Slack- and Teams-compatible)
Hosted (Cloud)—Not available yet

Where the reference lives

Sheet index · eggrd.dev/docs

Answering “what does this key actually do?” used to mean opening config.rs. It does not any more. Five pages carry the whole surface, and the largest of them is generated from the source so it cannot describe a binary that does not exist.

A field added without a doc comment fails the generator rather than producing a blank row: a reference with silent holes in it looks complete and is not. Writing it found 21 public config fields with no doc comment at all, which are now documented in the source as well as on the page.

Sheet index
PageWhat it carriesKept honest by
OverviewWhere to start, and how the pages differ.By hand
CLI & environmentEvery subcommand and flag, the environment variables that override the config, the one optional build feature.Checked against --help
ConfigurationAll 319 keys across 40 tables, with types and defaults.Generated from src/config.rs
API & data planeThe reserved endpoints, the headers eggrd adds to the request and to the response, and every status code it generates instead of your app, in evaluation order.Read from the routers and the pipeline
OperationsHealth, readiness and metrics endpoints, every Prometheus series, and what fails closed.By hand