Skip to main content
Version: 0.17 (unstable)

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​

VariableDefaultBuild modeNotes
DATABASE_URLrequiredpostgresPostgres 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/storagefilesystemPath for state + delta blobs.
GUARDIAN_METADATA_PATH/var/guardian/metadatafilesystemPath for accounts, auth credentials, network config. Holds .metadata/accounts.json (account records) and .metadata/auth_state.json (replay-protection timestamps). Back up and restore the two files together: the server refuses to start if auth_state.json is missing while migrated accounts exist, because empty replay state would re-accept previously seen requests (see Troubleshooting).
GUARDIAN_KEYSTORE_PATH/var/guardian/keystoreanyLocal Falcon/ECDSA key files (ACK signers and per-account creds).
GUARDIAN_DB_POOL_MAX_SIZE16 (code default); 32 set by the prod Terraform profilepostgresStorage backend pool size.
GUARDIAN_METADATA_DB_POOL_MAX_SIZEmatches storagepostgresMetadata backend pool size; usually leave equal.
GUARDIAN_CANONICALIZATION_FAST_PROMOTION_ENABLEDtrueanyEnables the additional promotion-only pass for recent candidates. Set to false to use only the full canonicalization interval; AWS deployments can set Terraform variable guardian_canonicalization_fast_promotion_enabled = false.
GUARDIAN_CANONICALIZATION_MAX_CONCURRENT_ACCOUNTS10 (code default); 50 set by the prod Terraform profileanyAccounts one canonicalization pass processes in parallel; 1 = fully sequential. Each account holds a DB connection only during its short fenced transactions — the dominant cost is a connectionless chain RPC — so this may exceed GUARDIAN_DB_POOL_MAX_SIZE; simultaneous write bursts just queue briefly at the pool.
GUARDIAN_SERVER_FEATURESbuild-timedeploy scriptComma list (postgres, evm) the deploy script compiles in. Not read at runtime — controls how the image is built.

Canonicalization settings apply as follows:

SettingFull passFast promotion
check_interval_secondsSets the full-pass cadence.Stops new fast work when the next full pass is due.
fast_promotion_enabledNo effect.Enables or disables the pass.
fast_promotion_interval_secondsNo effect.Sets its cadence and admission window. Work already in flight may finish after the deadline.
fast_promotion_window_secondsNo effect.Limits eligibility to recently created candidates.
max_retriesControls when repeated verification failures discard a candidate.Not read or modified.
submission_grace_period_secondsDefers retry consumption for young candidates.Not read or modified.
divergence_confirmationsControls confirmed-divergence discard decisions.Not read or modified.
max_concurrent_accountsBounds concurrent accounts.Bounds concurrent accounts. Candidates remain sequential within each account.

Fast promotion is promotion-only: a miss, absent account, RPC failure, invalid claim, or reconstruction mismatch does not change candidate status, retry count, or divergence count. The full pass remains responsible for every retry, defer, divergence, and discard decision.

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.

sslmodesslrootcertBehavior
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), or sslrootcert=system (unsupported — needs libpq ≥16) all abort startup with an actionable error rather than connecting insecurely.
  • Hostname matching under verify-full is 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​

VariableDefaultNotes
GUARDIAN_ENVunsetSelects the default ACK secret source: prod → AWS Secrets Manager; anything else (or unset) → ephemeral filesystem keys, regenerated each restart. Override explicitly with GUARDIAN_ACK_SECRET_PROVIDER (below) — e.g. file for a stable identity without AWS.
AWS_REGIONunsetRequired when GUARDIAN_ENV=prod. Region for Secrets Manager calls.
GUARDIAN_NETWORK_TYPEnone — requiredMiden network identifier: MidenLocal (local), MidenTestnet (testnet), MidenDevnet (devnet); case-insensitive. Pins the network identity (bech32 address prefixes, dashboard rendering) and the default Miden RPC endpoint. The server refuses to start when it is unset or unrecognized — there is no fallback network.
GUARDIAN_MIDEN_RPC_ENDPOINTper-network defaultOverrides the Miden node RPC endpoint (self-hosted node, private RPC, sidecar container) without changing network identity. Must be an origin-only http(s) URL (scheme://host[:port]); userinfo, non-root paths, queries, and fragments are rejected because tonic does not send them as authentication. An invalid value fails startup. Startup logs the validated origin. Combining an override with MidenTestnet/MidenDevnet logs a warning (legitimate for a mirror). A configured endpoint never falls back to the network default.
GUARDIAN_MIDEN_RPC_TIMEOUT_MS10000Per-request deadline on the node channel (the same 10s default every Miden RPC surface uses). Positive integer; 0 or a malformed value fails startup.
GUARDIAN_MIDEN_RPC_MAX_ATTEMPTS1 (retries off)Attempt budget for eligible idempotent node reads outside canonicalization. Canonicalization performs exactly one node read per observation regardless of this value — transient failures there are recovered by the next scheduled pass, so raising this budget never makes a canonicalization pass hold its lease longer. Transaction submission is never retried regardless of this value. Endpoint failover is not currently implemented. Retry activity is visible as guardian_miden_rpc_retries_total (never incremented by canonicalization reads).

Upgrade note: this release reduces the default Miden RPC deadline from 30 seconds to 10 seconds for existing deployments. The deadline is applied at the channel level, so it caps submissions as well as reads even though submissions remain single-attempt. Set GUARDIAN_MIDEN_RPC_TIMEOUT_MS=30000 to retain the previous deadline.

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):

VariableDefaultNotes
GUARDIAN_ACK_FALCON_SECRET_IDguardian-prod/server/ack-falcon-secret-keySecrets Manager name/ARN for the Falcon ACK secret key.
GUARDIAN_ACK_ECDSA_SECRET_IDguardian-prod/server/ack-ecdsa-secret-keySecrets Manager name/ARN for the ECDSA ACK secret key. Used only when the ECDSA backend is in-memory.

ACK secret provider (stable identity without AWS)​

Outside prod the default (GUARDIAN_ACK_SECRET_PROVIDER=none) generates a fresh ACK keypair on every restart, which changes the Guardian's on-chain ack-key commitment and freezes accounts that pinned the old one. Set the provider to file to load fixed keys from local files instead — a stable identity without AWS Secrets Manager. Each file holds the hex string emitted by ack-keygen (identical to what Secrets Manager stores). See the Secrets runbook.

VariableDefaultNotes
GUARDIAN_ACK_SECRET_PROVIDERaws when GUARDIAN_ENV=prod, else noneSource of the ACK signing keys: aws (Secrets Manager), file (local files), or none (ephemeral, dev only). An unrecognized value fails startup; none is rejected when GUARDIAN_ENV=prod.
GUARDIAN_ACK_FALCON_SECRET_PATHunsetRequired when GUARDIAN_ACK_SECRET_PROVIDER=file. Path to a file holding the hex-encoded Falcon ACK secret key. On Unix the file must be owner-only (mode 0600) or startup fails.
GUARDIAN_ACK_ECDSA_SECRET_PATHunsetRequired when GUARDIAN_ACK_SECRET_PROVIDER=file, unless GUARDIAN_ACK_ECDSA_BACKEND=aws-kms (then the ECDSA key comes from KMS and this file is never read). Path to the hex-encoded ECDSA ACK secret key; same 0600 requirement.

Storage encryption at rest​

Application-layer encryption of the sensitive stored payloads (account state, delta and proposal payloads). It is opt-in by key-source presence — configure a key and the server encrypts; configure none and it stores plaintext exactly as before. Routing/index fields (account id, nonce, commitments, status, timestamps) always stay plaintext.

Which variable do I set? Choose one key source — the dev key for local work, or the Secrets Manager secret for production. You never set both (doing so is a startup error). GUARDIAN_STORAGE_ENCRYPTION_KEY_ID is not a key — it is an optional label, and most users leave it unset.

Key sources (set exactly one; presence is what turns encryption on):

VariableDefaultNotes
GUARDIAN_STORAGE_ENCRYPTION_KEYunsetDev key source. The actual 32-byte key, base64-encoded (openssl rand -base64 32).
GUARDIAN_STORAGE_ENCRYPTION_KEY_SECRET_IDunsetProd key source. AWS Secrets Manager name/ARN of a secret holding a structured { "active": kid, "keys": { kid: base64-32-bytes } } document (one or more keys). Reuses AWS_REGION.

Optional label:

VariableDefaultNotes
GUARDIAN_STORAGE_ENCRYPTION_KEY_IDk1Only used with the dev key source. Sets the key id (kid) recorded on each encrypted record so the key can be identified later. The default is fine for almost everyone — leave it unset unless you specifically need the dev key's id to match an id used elsewhere. (With Secrets Manager the ids come from the secret's keys/active fields instead, so this variable is ignored.)

Every encrypted record stores the kid of the key that wrote it, and on read the key is resolved by that id — this is what lets Secrets Manager hold several keys and rotate them (add a new key, repoint active, keep the old key so existing records still decrypt). The dev key source holds a single key, so it cannot do multi-key rotation reads; that is a production capability.

Rules: configure exactly one key source (both set → startup error). When a key source is set the server validates it at startup and fails fast on a missing/malformed/wrong-length key — it never silently falls back to plaintext. Encryption is fixed for a populated store: the server records a marker on the first encrypted write and refuses to mix plaintext and ciphertext, so enable it against an empty store (e.g. after the Miden 0.16 reset, see MIDEN_COMPATIBILITY.md). Switching an existing store requires an explicit re-encryption migration (not yet provided).

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.

VariableDefaultNotes
GUARDIAN_ACK_ECDSA_BACKENDin-memoryin-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_IDunsetRequired 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-memory to aws-kms means a new keypair, hence a new ECDSA pubkey/commitment. Re-establish downstream trust accordingly.

_SECRET_ID (runtime) vs _SECRET_NAME (deploy-side): the server reads GUARDIAN_ACK_*_SECRET_ID at startup, but you typically don't set these by hand. The deploy script accepts GUARDIAN_ACK_*_SECRET_NAME (see the Deploy script section below), passes it into Terraform as guardian_ack_*_secret_name, and Terraform sets the matching _SECRET_ID env 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​

VariableDefaultNotes
GUARDIAN_RATE_LIMIT_ENABLEDtrueMaster kill-switch for rate limiting on both transports (HTTP and gRPC; there is no per-transport toggle). Set false only in test environments. Client identity for keying comes from the ingress (rightmost X-Forwarded-For entry, then X-Real-IP, then the socket peer); deployments not behind the reference ALB must forward the client address on both listeners, strip any client-supplied X-Forwarded-For if they identify callers with X-Real-IP, and restrict direct access to the server ports; see PRODUCTION.md.
GUARDIAN_RATE_BURST_PER_SEC10 (code default); 200 set by the prod Terraform profileRequests allowed in any one-second window, keyed per IP and endpoint, where the endpoint is the HTTP path or the gRPC method. Both transports are metered from one store, but because their endpoint names differ, a burst bucket is never shared across transports.
GUARDIAN_RATE_PER_MIN60 (code default); 5000 set by the prod Terraform profileSustained rate, keyed per IP only, so HTTP and gRPC calls from one client draw on the same allowance. This is the cross-transport limit: deployments sized for HTTP-only traffic should re-check it, since gRPC traffic (the Rust SDK's default transport) counts against it since the transport-bypass fix.
GUARDIAN_MAX_REPLICAS1 (code default); greater of desired count and autoscaling max when enabled, or desired count otherwise, set by TerraformPer-replica rate-limit divisor used by global HTTP+gRPC and dashboard per-commitment limits. Each replica enforces global / GUARDIAN_MAX_REPLICAS, keeping the aggregate at or below the configured limit through steady-state autoscaling. During a rolling deployment the temporary aggregate may rise by up to deployment_maximum_percent / 100 (2× by default). Drives rate-limiting only — coordination mode is backend-derived. Running below the steady-state maximum over-throttles; HTTP keep-alive can pin a client to one replica. An override is clamped up to steady-state capacity by Terraform. Must be a positive integer when set: an invalid value fails startup in prod and is treated as 1 with a warning elsewhere. See runbooks/horizontal-scaling.md.
GUARDIAN_DASHBOARD_COMMITMENT_RATE_BURST_PER_SEC6Fleet-wide dashboard challenge and verification burst budget for one operator commitment. Divided by GUARDIAN_MAX_REPLICAS and clamped to at least 1 per replica. A custom value below the divisor can therefore exceed its nominal fleet-wide budget.
GUARDIAN_DASHBOARD_COMMITMENT_RATE_PER_MIN30Fleet-wide dashboard challenge and verification sustained budget for one operator commitment. Divided by GUARDIAN_MAX_REPLICAS and clamped to at least 1 per replica. A custom value below the divisor can therefore exceed its nominal fleet-wide budget.
GUARDIAN_MAX_REQUEST_BYTES1048576 (1 MB)Reject request bodies larger than this.
GUARDIAN_MAX_PENDING_PROPOSALS_PER_ACCOUNT20Account-level cap; hitting it returns pending_proposals_limit.
GUARDIAN_CORS_ALLOWED_ORIGINSunsetComma-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)​

VariableDefaultNotes
GUARDIAN_METRICS_ENABLEDfalseMaster switch for the Prometheus integration. When false (default) nothing runs: no metrics listener, no recorder, no storage instrumentation, no background refresher.
GUARDIAN_METRICS_ADDR127.0.0.1:9464Bind 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/metricsPath serving the Prometheus text exposition on that listener. Must start with /.
GUARDIAN_METRICS_REFRESH_INTERVAL_SECS30Cadence 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_TOKENunsetOptional 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​

VariableDefaultNotes
GUARDIAN_OPERATOR_PUBLIC_KEYS_SECRET_IDunsetAWS Secrets Manager secret name/ARN holding the operator allowlist JSON. Hot-reloaded on every challenge and authenticated /dashboard/* request.
GUARDIAN_OPERATOR_PUBLIC_KEYS_FILEunsetLocal JSON path for the same payload. Local dev only.
GUARDIAN_DASHBOARD_CURSOR_SECRETrandom per process if unset32-byte hex HMAC key for dashboard pagination cursors. Pin a shared value across replicas so cursors validate everywhere. The prod Terraform profile injects a pre-created Secrets Manager value into every task. If unset outside that profile, the server warns and generates an ephemeral per-process key and still boots (in every stage); an ephemeral key breaks pagination cursors across replicas and restarts — the dashboard feeds and the client GET /delta/history endpoint — nothing else, so it is not a startup guard.

GET /dashboard/info.environment is derived from GUARDIAN_NETWORK_TYPE (testnet, devnet, or local) rather than configured separately.

Prod-stage startup guards & HA behavior​

When GUARDIAN_ENV=prod, the server fails fast on misconfigurations that are silently broken across replicas:

  • the filesystem storage backend is refused (single-instance only — use the Postgres image with DATABASE_URL);
  • a rate limit that partitions to 0 requests per replica is refused — i.e. the global GUARDIAN_RATE_BURST_PER_SEC/GUARDIAN_RATE_PER_MIN is below GUARDIAN_MAX_REPLICAS, which would make every replica throttle all traffic. Raise the global limit or lower GUARDIAN_MAX_REPLICAS. (Non-prod only warns.)

On the Postgres backend, operator/EVM sessions, login challenges, and the canonicalization lease are shared across replicas (backend-derived — no tunable disables this). If the database is briefly unavailable, authentication fails closed (rejected, never bypassed) and recovers automatically. See the horizontal-scaling runbook.

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.

VariableDefaultNotes
GUARDIAN_EVM_RPC_URLSunset (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_ADDRESS0x433709009b8330fda32311df1c2afa402ed8d009 (EntryPoint v0.9)Shared EntryPoint address used for finality checks across chains.

Logging​

VariableDefaultNotes
RUST_LOGinfoStandard tracing-subscriber filter. Module-scoped filters work: RUST_LOG=server::jobs::canonicalization=debug. Hot-path request events (get_state, get_delta, push_delta, proposal create/sign, lookup_account) are debug; use RUST_LOG=server=debug or RUST_LOG=server::services=debug to see them. At info each request instead emits a single span-close line carrying the span's fields (account ID, nonce, commitment, signer/match counts) plus time.busy / time.idle. That line is emitted on the success and the error path, which is what correlates the centralized 5xx log (see below) back to an account.
GUARDIAN_LOG_FORMATtextLog output format. text — human-readable (ANSI when TTY, plain otherwise). json — flattened JSON with span context for CloudWatch Logs Insights. compact — single-line text. Value is trimmed and case-insensitive; unknown values fall back to text with a stderr warn (emitted before the tracing subscriber is installed). Defaults to text when unset. ECS default is json (see infra/variables.tf guardian_log_format).

The 5xx and 4xx lines emitted from GuardianError's HTTP IntoResponse and tonic::Status conversions run after the service span has closed, so they carry only code and detail, never account fields. The preceding span-close line is what supplies the account context for them.

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.

VariableDefaultNotes
STACK_NAMEguardianBase name for all AWS resources and Terraform state file.
DEPLOY_STAGEdevdev or prod; selects stage profile (autoscaling, RDS Proxy, etc.).
CPU_ARCHITECTUREX86_64X86_64 or ARM64. Picks the Docker buildx platform and the ECS task arch.
AWS_REGIONrequiredAll AWS API calls.
DOMAIN_NAMEopenzeppelin.comRoot domain for the canonical public hostname.
SUBDOMAINguardianHost portion of the public hostname.
ACM_CERTIFICATE_ARNunsetACM certificate for HTTPS on the canonical hostname.
ROUTE53_ZONE_IDunsetOptional Route 53 hosted zone for an alias record.
CLOUDFLARE_ZONE_IDunsetOptional Cloudflare zone for CNAME management.
CLOUDFLARE_API_TOKENunsetRequired when either primary or secondary Cloudflare DNS management is enabled.
CLOUDFLARE_PROXIEDtrueWhether the Cloudflare CNAME should be proxied.
ALIAS_SUBDOMAINunsetMigration-only legacy subdomain under DOMAIN_NAME; leave unset for normal deployments. DNS may be Terraform-managed or external.
ALIAS_ACM_CERTIFICATE_ARNACM_CERTIFICATE_ARNMigration-only distinct certificate for the legacy hostname, attached through SNI when needed.
GUARDIAN_ACK_FALCON_SECRET_NAMEunset → ${STACK_NAME}/server/ack-falcon-secret-keyDeploy-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_NAMEunset → ${STACK_NAME}/server/ack-ecdsa-secret-keyDeploy-side override for the ECDSA ACK secret. Same flow as the Falcon entry above.
GUARDIAN_OPERATOR_PUBLIC_KEYS_JSONunsetInline 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_ARNunsetARN 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_FILEunsetPath 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_IDSunsetComma 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_ARNunsetECS-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_ARNunsetSame, for the bookkeeping chain-ID list.
TF_VAR_*unsetAny 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. 3000 and 50051 are 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, fast_promotion_interval_seconds, fast_promotion_window_seconds, max_retries, submission_grace_period_seconds, divergence_confirmations). Currently hard-coded in the canonicalization worker; require a code change to alter. The exceptions are fast_promotion_enabled and max_concurrent_accounts, configurable via GUARDIAN_CANONICALIZATION_FAST_PROMOTION_ENABLED and GUARDIAN_CANONICALIZATION_MAX_CONCURRENT_ACCOUNTS (see the environment table above).
  • Auth timestamp window. MAX_TIMESTAMP_SKEW_MS = 300_000 (5 min) is hard-coded in metadata/auth/credentials.rs:6.

Quick combos​

I want…Set
Minimum local devnothing — docker compose up works
Postgres backend locallyDATABASE_URL=… + build with --features postgres
EVM support locallyGUARDIAN_EVM_RPC_URLS (allowed chain set derives from its keys) + build with --features evm
Use Secrets Manager for ACK keysGUARDIAN_ENV=prod + AWS_REGION=<region> + secrets pre-created
Run the dashboard locallyGUARDIAN_OPERATOR_PUBLIC_KEYS_FILE=/path/to/allowlist.json
Multi-replica (HA)Postgres backend + GUARDIAN_DASHBOARD_CURSOR_SECRET=<64 hex> pinned across tasks + GUARDIAN_MAX_REPLICAS=<steady-state max capacity>. The prod Terraform profile sets these after bootstrap-dashboard-cursor-secret creates the required Secrets Manager entry.
Higher throughput in prodGUARDIAN_RATE_BURST_PER_SEC, GUARDIAN_RATE_PER_MIN, GUARDIAN_DB_POOL_MAX_SIZE, GUARDIAN_CANONICALIZATION_MAX_CONCURRENT_ACCOUNTS