Configuration Reference
Every environment variable Guardian honours, in one place.
Use this as a lookup table — pair it with LOCAL_DEV.md
for which combinations make sense locally and
SERVER_AWS_DEPLOY.md for how the deploy script
sets them in production.
For Terraform variables (everything under infra/), see
infra/README.md — those are a
separate surface; the deploy script translates between Terraform vars and
the runtime env vars in this document.
Conventions
- Required means the server will refuse to start if the variable is missing in the relevant build/feature combo.
- Default is what the server picks when the variable is unset.
- Build mode indicates which Cargo feature gate consumes the value. Defaults builds are the no-feature filesystem build unless stated.
Runtime — server identity and storage
| Variable | Default | Build mode | Notes |
|---|---|---|---|
DATABASE_URL | required | postgres | Postgres connection string. Server panics at startup if unset under --features postgres. TLS verification is controlled by the standard sslmode/sslrootcert parameters — see Database TLS. |
GUARDIAN_STORAGE_PATH | /var/guardian/storage | filesystem | Path for state + delta blobs. |
GUARDIAN_METADATA_PATH | /var/guardian/metadata | filesystem | Path for accounts, auth credentials, network config. |
GUARDIAN_KEYSTORE_PATH | /var/guardian/keystore | any | Local Falcon/ECDSA key files (ACK signers and per-account creds). |
GUARDIAN_DB_POOL_MAX_SIZE | 16 (code default); 32 set by the prod Terraform profile | postgres | Storage backend pool size. |
GUARDIAN_METADATA_DB_POOL_MAX_SIZE | matches storage | postgres | Metadata backend pool size; usually leave equal. |
GUARDIAN_SERVER_FEATURES | build-time | deploy script | Comma list (postgres, evm) the deploy script compiles in. Not read at runtime — controls how the image is built. |
Database TLS
TLS behavior is driven entirely by the standard libpq parameters in
DATABASE_URL; there is no Guardian-specific TLS env var. To migrate a deployed
AWS stack to verified TLS, see
runbooks/enable-db-tls.md. The same parameters
govern both the synchronous startup-migration connection and the asynchronous
runtime pools, so they always behave identically.
sslmode | sslrootcert | Behavior |
|---|---|---|
omitted / disable | (any) | Plaintext, no TLS. |
require | (none) | Encrypted, certificate not verified. |
require | <path> | Encrypted + certificate chain verified (promoted to verify-ca, matching libpq). |
verify-ca | <path> | Encrypted + chain verified (hostname not checked). |
verify-full | <path> | Encrypted + chain and hostname verified. Recommended for managed providers. |
sslrootcert=<path>points at a PEM CA bundle file the server can read; the whole bundle must parse (a malformed entry fails startup). It may contain multiple roots.- Verifying modes fail closed: a missing/unreadable/empty CA bundle, an
unknown
sslmode,allow/prefer(which permit plaintext fallback), orsslrootcert=system(unsupported — needs libpq ≥16) all abort startup with an actionable error rather than connecting insecurely. - Hostname matching under
verify-fullis strict SAN-based; certificates without a matching Subject Alternative Name (including Common-Name-only certs) are rejected.
Per-provider examples:
# AWS RDS (managed; recommended). Mount a combined bundle of the Amazon RDS CA
# roots AND the Amazon Trust Services roots — the RDS Proxy presents an ACM
# certificate chaining to Amazon Trust Services, a direct instance chains to the
# RDS CA roots.
DATABASE_URL=postgres://USER:PW@HOST:5432/guardian?sslmode=verify-full&sslrootcert=/etc/guardian/tls/rds-combined-ca.pem
# Another managed provider (GCP Cloud SQL / Azure / Supabase / Neon …)
DATABASE_URL=postgres://USER:PW@HOST:5432/guardian?sslmode=verify-full&sslrootcert=/etc/guardian/tls/provider-ca.pem
# Local docker compose (no TLS)
DATABASE_URL=postgres://guardian:guardian@localhost:5432/guardian
Runtime — ACK signing and network
| Variable | Default | Notes |
|---|---|---|
GUARDIAN_ENV | unset | Set to prod to load ACK keys from AWS Secrets Manager. Anything else (or unset) uses filesystem keystore and auto-generates if absent. |
AWS_REGION | unset | Required when GUARDIAN_ENV=prod. Region for Secrets Manager calls. |
GUARDIAN_NETWORK_TYPE | MidenDevnet | Miden network identifier (MidenDevnet, MidenTestnet, etc.). Required only when you need a non-default network. Pins which Miden RPC and on-chain consensus the server speaks to. |
ACK secret IDs are configurable. The server reads two env vars at startup
and falls back to fixed defaults when they're unset
(crates/server/src/ack/secrets_manager.rs:10-13):
| Variable | Default | Notes |
|---|---|---|
GUARDIAN_ACK_FALCON_SECRET_ID | guardian-prod/server/ack-falcon-secret-key | Secrets Manager name/ARN for the Falcon ACK secret key. |
GUARDIAN_ACK_ECDSA_SECRET_ID | guardian-prod/server/ack-ecdsa-secret-key | Secrets Manager name/ARN for the ECDSA ACK secret key. Used only when the ECDSA backend is in-memory. |
Hosted ECDSA signer backend
The ECDSA ACK signer can keep its private key outside the process in a hosted backend. Falcon is unaffected and always uses the in-memory path.
| Variable | Default | Notes |
|---|---|---|
GUARDIAN_ACK_ECDSA_BACKEND | in-memory | in-memory (filesystem keystore, or Secrets Manager when GUARDIAN_ENV=prod) or aws-kms. An unrecognized value fails startup listing the supported values. |
GUARDIAN_ACK_ECDSA_KMS_KEY_ID | unset | Required when GUARDIAN_ACK_ECDSA_BACKEND=aws-kms. KMS key id, ARN, or alias. The key must be ECC_SECG_P256K1 with usage SIGN_VERIFY. |
With aws-kms, the server holds only the key handle; the private key never enters
the process. At startup it fetches the public key, validates the key spec, and
performs a sign probe to confirm kms:Sign permission — failing fast otherwise.
The ECDSA secret in Secrets Manager (GUARDIAN_ACK_ECDSA_SECRET_ID) is not
read on this path. Credentials resolve through the standard AWS chain (the ECS
task role in production); required IAM is kms:GetPublicKey and kms:Sign on the
key.
Switching an existing deployment from
in-memorytoaws-kmsmeans a new keypair, hence a new ECDSApubkey/commitment. Re-establish downstream trust accordingly.
_SECRET_ID(runtime) vs_SECRET_NAME(deploy-side): the server readsGUARDIAN_ACK_*_SECRET_IDat startup, but you typically don't set these by hand. The deploy script acceptsGUARDIAN_ACK_*_SECRET_NAME(see the Deploy script section below), passes it into Terraform asguardian_ack_*_secret_name, and Terraform sets the matching_SECRET_IDenv var on the ECS task. Same value, three places — see the Secrets runbook for the full override chain.
In the reference AWS deploy, Terraform sets both _SECRET_ID env vars on
the ECS task to ${stack_name}/server/ack-{falcon,ecdsa}-secret-key so
multi-stack deployments get scoped IDs.
Runtime — request safety
| Variable | Default | Notes |
|---|---|---|
GUARDIAN_RATE_LIMIT_ENABLED | true | Master kill-switch for HTTP rate limiting. Set false only in test environments. |
GUARDIAN_RATE_BURST_PER_SEC | 10 (code default); 200 set by the prod Terraform profile | Token-bucket burst. |
GUARDIAN_RATE_PER_MIN | 60 (code default); 5000 set by the prod Terraform profile | Sustained rate. |
GUARDIAN_MAX_REQUEST_BYTES | 1048576 (1 MB) | Reject request bodies larger than this. |
GUARDIAN_MAX_PENDING_PROPOSALS_PER_ACCOUNT | 20 | Account-level cap; hitting it returns pending_proposals_limit. |
GUARDIAN_CORS_ALLOWED_ORIGINS | unset | Comma-separated explicit origins. Unset → permissive Any origin / Any methods / Any headers, credentials disabled (suitable for local dev). Set → strict allowlist with allow_credentials(true) (required for production browser clients). |
Runtime — metrics (Prometheus)
| Variable | Default | Notes |
|---|---|---|
GUARDIAN_METRICS_ENABLED | false | Master switch for the Prometheus integration. When false (default) nothing runs: no metrics listener, no recorder, no storage instrumentation, no background refresher. |
GUARDIAN_METRICS_ADDR | 127.0.0.1:9464 | Bind address of the dedicated metrics listener (separate from the API port). Loopback by default; set 0.0.0.0:9464 in containers so a Prometheus sidecar/agent can reach it. Invalid values fall back to the default with a warning. |
GUARDIAN_METRICS_PATH | /metrics | Path serving the Prometheus text exposition on that listener. Must start with /. |
GUARDIAN_METRICS_REFRESH_INTERVAL_SECS | 30 | Cadence of the background task that refreshes slow aggregate gauges (delta status counts, in-flight proposals, account count). Scrapes never query storage directly. |
GUARDIAN_METRICS_BEARER_TOKEN | unset | Optional shared-secret scrape token. When set, scrapes must send Authorization: Bearer <token> (Prometheus authorization.credentials in the scrape config); anything else gets 401. Compared in constant time, held as a non-loggable secret wrapper in process. |
The metrics endpoint is intentionally not part of the main API
router: it bypasses rate limiting and CORS and is protected by network
isolation first (loopback default / private network / security group),
the bearer token second, and proxy-terminated TLS where transport
encryption is required. Never expose it to a public network. The
exposed metric taxonomy and cardinality rules are documented in
spec/api.md; see the
Observability guide for scraping and a
one-command Grafana dashboard stack.
Runtime — dashboard
| Variable | Default | Notes |
|---|---|---|
GUARDIAN_OPERATOR_PUBLIC_KEYS_SECRET_ID | unset | AWS Secrets Manager secret name/ARN holding the operator allowlist JSON. Hot-reloaded on every challenge and authenticated /dashboard/* request. |
GUARDIAN_OPERATOR_PUBLIC_KEYS_FILE | unset | Local JSON path for the same payload. Local dev only. |
GUARDIAN_DASHBOARD_CURSOR_SECRET | random per process | 32-byte hex HMAC key for dashboard pagination cursors. Pin a shared value when running ≥2 ECS tasks so cursors validate across replicas. |
GET /dashboard/info.environment is derived from GUARDIAN_NETWORK_TYPE
(testnet, devnet, or local) rather than configured separately.
Allowlist payload shapes and enrollment flow:
docs/DASHBOARD.md.
Runtime — EVM (feature-gated)
These take effect only when the server is built with --features evm.
The server reads only the two variables in this table; the allowed chain
set is derived from the keys of GUARDIAN_EVM_RPC_URLS rather than a
separate variable.
| Variable | Default | Notes |
|---|---|---|
GUARDIAN_EVM_RPC_URLS | unset (treated as an empty registry) | Comma list chain_id=rpc_url. E.g. 1=https://…,11155111=https://…. Allowed chain IDs are the keys of this map. Required for usable EVM chains — when unset, the server starts but the EVM registry is empty and every chain ID will be rejected. |
GUARDIAN_EVM_ENTRYPOINT_ADDRESS | 0x433709009b8330fda32311df1c2afa402ed8d009 (EntryPoint v0.9) | Shared EntryPoint address used for finality checks across chains. |
Logging
| Variable | Default | Notes |
|---|---|---|
RUST_LOG | info | Standard tracing-subscriber filter. Module-scoped filters work: RUST_LOG=server::jobs::canonicalization=debug. |
Useful filters during debugging — see
TROUBLESHOOTING.md.
Deploy script (scripts/aws-deploy.sh)
These are read by the deploy script, not by the server itself. The script turns them into Terraform variables or build-time choices.
| Variable | Default | Notes |
|---|---|---|
STACK_NAME | guardian | Base name for all AWS resources and Terraform state file. |
DEPLOY_STAGE | dev | dev or prod; selects stage profile (autoscaling, RDS Proxy, etc.). |
CPU_ARCHITECTURE | X86_64 | X86_64 or ARM64. Picks the Docker buildx platform and the ECS task arch. |
AWS_REGION | required | All AWS API calls. |
SUBDOMAIN | guardian | Host portion of the public hostname. |
ROUTE53_ZONE_ID | unset | Optional Route 53 hosted zone for an alias record. |
CLOUDFLARE_ZONE_ID | unset | Optional Cloudflare zone for CNAME management. |
CLOUDFLARE_API_TOKEN | unset | Required iff CLOUDFLARE_ZONE_ID is set. |
CLOUDFLARE_PROXIED | false | Whether the Cloudflare CNAME should be proxied. |
GUARDIAN_ACK_FALCON_SECRET_NAME | unset → ${STACK_NAME}/server/ack-falcon-secret-key | Deploy-side override for the Falcon ACK secret. Passed into Terraform as guardian_ack_falcon_secret_name and set on the ECS task as the runtime GUARDIAN_ACK_FALCON_SECRET_ID. |
GUARDIAN_ACK_ECDSA_SECRET_NAME | unset → ${STACK_NAME}/server/ack-ecdsa-secret-key | Deploy-side override for the ECDSA ACK secret. Same flow as the Falcon entry above. |
GUARDIAN_OPERATOR_PUBLIC_KEYS_JSON | unset | Inline JSON array of operator pubkeys; Terraform creates the secret from this. Mutually exclusive with GUARDIAN_OPERATOR_PUBLIC_KEYS_SECRET_ARN. |
GUARDIAN_OPERATOR_PUBLIC_KEYS_SECRET_ARN | unset | ARN of an externally-managed operator pubkeys secret. When set, Terraform does not create one and the task reads from this ARN instead. |
GUARDIAN_EVM_CHAIN_CONFIG_FILE | unset | Path to a JSON file the deploy script reads to derive GUARDIAN_EVM_RPC_URLS (and the bookkeeping GUARDIAN_EVM_ALLOWED_CHAIN_IDS Terraform variable). Not read by the server. |
GUARDIAN_EVM_ALLOWED_CHAIN_IDS | unset | Comma list of chain IDs used only by Terraform for bookkeeping / secret naming. The server itself derives allowed chains from GUARDIAN_EVM_RPC_URLS keys. |
GUARDIAN_EVM_RPC_URLS_SECRET_ARN | unset | ECS-injection only: when set, the ECS task reads GUARDIAN_EVM_RPC_URLS from this Secrets Manager ARN at task start (the server still sees a plain env var). |
GUARDIAN_EVM_ALLOWED_CHAIN_IDS_SECRET_ARN | unset | Same, for the bookkeeping chain-ID list. |
TF_VAR_* | unset | Any standard Terraform var override; the script passes through. |
Secrets in env vars — scope of in-process protection
Secret-bearing env vars (DATABASE_URL, GUARDIAN_DASHBOARD_CURSOR_SECRET,
GUARDIAN_EVM_RPC_URLS) are wrapped at the point of read into
zero-on-drop, no-Display/no-Serialize types inside the server
process, so they cannot accidentally appear in logs, panic messages, or
serialized responses. This does not protect the OS process
environment block — /proc/<pid>/environ, coredumps, fork-inherited
env, and ECS task-definition environment fields all remain visible at
the OS layer regardless. For the highest-sensitivity material (ACK
signing keys) prefer the AWS Secrets Manager runtime-fetch path
(GUARDIAN_ACK_FALCON_SECRET_ID / GUARDIAN_ACK_ECDSA_SECRET_ID),
which is already how production loads those keys.
What's not env-configurable
A few things are deliberately compile-time or builder-API only — knowing this saves you from grepping:
- HTTP / gRPC ports.
3000and50051are builder defaults (builder/mod.rs:68); configurable through the Rust builder but not via env. ECS pins these in the task definition. - Storage backend choice. Cargo feature
postgres(or its absence), not an env var. See Storage modes. - EVM support. Cargo feature
evm. If the binary wasn't built with it, no env var will turn it on. - Canonicalization knobs (
check_interval_seconds,max_retries,submission_grace_period_seconds). Currently hard-coded in the canonicalization worker; require a code change to alter. - Auth timestamp window.
MAX_TIMESTAMP_SKEW_MS = 300_000(5 min) is hard-coded inmetadata/auth/credentials.rs:6.
Quick combos
| I want… | Set |
|---|---|
| Minimum local dev | nothing — docker compose up works |
| Postgres backend locally | DATABASE_URL=… + build with --features postgres |
| EVM support locally | GUARDIAN_EVM_RPC_URLS (allowed chain set derives from its keys) + build with --features evm |
| Use Secrets Manager for ACK keys | GUARDIAN_ENV=prod + AWS_REGION=<region> + secrets pre-created |
| Run the dashboard locally | GUARDIAN_OPERATOR_PUBLIC_KEYS_FILE=/path/to/allowlist.json |
| Multi-replica dashboard | GUARDIAN_DASHBOARD_CURSOR_SECRET=<32-byte hex> pinned across tasks |
| Higher throughput in prod | GUARDIAN_RATE_BURST_PER_SEC, GUARDIAN_RATE_PER_MIN, GUARDIAN_DB_POOL_MAX_SIZE |