Operations
Three endpoints, one metrics format, and one decision to make about which port they live on. eggrd exports Prometheus text on a plain HTTP path — there is no agent, no sidecar and no push.
Ops endpoints
| Path | Answers | Use it for |
|---|---|---|
/__edgeguard/health |
200 always |
Liveness. Is the eggrd process itself up and serving? It does not consult the upstream, so it stays 200 during an upstream outage — restarting eggrd would not help. |
/__edgeguard/ready |
200 / 503 |
Readiness. Opens a TCP connection to the upstream with a 2-second timeout. 503 while the upstream is unreachable, so a load balancer stops sending traffic eggrd cannot serve. |
/__edgeguard/metrics |
Prometheus text | Scrape target. See below. |
The split matters more than it looks. Pointing a liveness probe at readiness restarts a perfectly healthy proxy every time the app behind it hiccups, which turns one outage into two.
Keeping them off the public port
By default these paths live on the public listener. Set
server.admin_port to a non-zero value and
eggrd binds a second, plain-HTTP listener for them — the public port then serves only
the proxy.
[server]
admin_port = 9090
admin_addr = "127.0.0.1" # same-host only; 0.0.0.0 for a private network interface
admin_addr defaults to 127.0.0.1, so the endpoints are reachable
only from the same host unless you widen it deliberately. Point your platform's health
check at the admin port once you enable it, or it will be checking a port that no longer
answers.
Metrics
Prometheus text format, scraped from /__edgeguard/metrics. The core series:
| Series | Type | What it tells you |
|---|---|---|
edgeguard_requests_total | counter | Requests by outcome label — the single most useful series here. See below. |
edgeguard_request_duration_seconds | histogram | End-to-end latency through the proxy, with _bucket, _sum and _count. |
edgeguard_ratelimit_hits_total | counter | Requests the limiter rejected. |
edgeguard_waf_hits_total | counter | WAF matches, labelled by rule (sqli, traversal, and so on). |
edgeguard_csp_reports_total | counter | CSP violation reports received at the browser-facing report sink. |
Request outcomes
Every request lands in exactly one outcome bucket on
edgeguard_requests_total. Alerting on the shape of this label is usually more
informative than alerting on status codes, because it distinguishes which gate
refused.
| Outcome | Meaning |
|---|---|
ok | Proxied to the upstream and returned. |
unauthorized | Auth refused it. 401. |
forbidden | WAF, DLP, or a model allowlist refused it. 403. |
ip_denied | Client address outside the access list. 403. |
rate_limited | Over the configured rate. 429. |
limiter_error | Shared limiter store unreachable and failing closed. 503. |
over_budget | An LLM budget was exhausted. 429 for a token cap, 402 for a cost cap. |
unpriced_model | Model absent from the price book while the policy requires one. 402. |
payload_too_large | Request body over validation.max_body. 413. |
header_too_large | Headers over the configured cap. |
method_not_allowed | Method outside the allowlist. 405. |
not_found | No route matched. |
bad_gateway | Upstream unreachable or answered unusably. 502. |
upstream_error | Upstream failed mid-response. |
upstream_timeout | Upstream exceeded validation.upstream_timeout. 504. |
ws_upgrade | Connection upgraded to a WebSocket and passed through. |
LLM gateway metrics
Present only when [llm] is enabled. Token counts
come from the upstream's own usage object — eggrd does not tokenize
anything itself, so the numbers are the provider's, not an estimate.
| Series | What it tells you |
|---|---|
edgeguard_llm_requests_total | Requests by result — including unpriced, which is how you find a model missing from the price book. |
edgeguard_llm_tokens_total | Total tokens, with _model_, _key_ and _team_ variants for attribution. |
edgeguard_llm_cost_microdollars_total | Cost in microdollars, with the same three attribution variants. Integer microdollars rather than floats, so summing does not drift. |
edgeguard_llm_cached_tokens_total | The cached subset of prompt tokens, where the provider reports it. |
edgeguard_llm_reasoning_tokens_total | Reasoning tokens, where the provider reports them separately. |
edgeguard_llm_budget_consumed_ratio | Gauge, 0–1, per budget. The series to alert on before a cap starts refusing traffic. |
edgeguard_llm_budget_blocked_total | Requests a budget refused. |
edgeguard_llm_budget_reconcile_failures_total | Reservations that could not be reconciled to actual usage. Non-zero means recorded spend is drifting from reality — worth an alert. |
edgeguard_llm_dlp_findings_total | DLP matches by category. |
edgeguard_llm_dlp_blocked_total | Requests DLP refused outright. |
edgeguard_llm_keyvault_total | Virtual-key lookups, including denials. |
edgeguard_llm_ttft_seconds | Histogram: time to first token. |
edgeguard_llm_tpot_seconds | Histogram: time per output token. |
What fails closed
Two shared stores can be unreachable, and both refuse traffic rather than waving it through by default. An outage that silently disabled rate limiting, or silently uncapped spend, would be the worst possible failure for either feature.
| Store | Setting | Default | On store error |
|---|---|---|---|
| Rate limiter | ratelimit.fail_open | false | 503, and the request never reaches the upstream. |
| LLM budgets | llm.fail_open | false | 503, and the provider is never called. |
Setting either to true trades correctness for availability: requests are
admitted unlimited while the store is down. That is a legitimate choice for a rate limiter
in front of a resilient app, and a dangerous one for a spend cap.
Endpoint behaviour and metric names were read from the shipped source rather than from memory. The configuration reference is generated from it directly.