chmonitorchmonitor

Reference

Environment Variables

Full reference for every chmonitor environment variable, grouped by category with build-time vs runtime notes.

23 min read

Self-hosters need three vars. Everything else is optional and listed here.

CLICKHOUSE_HOST=http://localhost:8123
CLICKHOUSE_USER=monitoring
CLICKHOUSE_PASSWORD=change-me
docker run -d -p 3000:3000 \
  -e CLICKHOUSE_HOST='http://localhost:8123' \
  -e CLICKHOUSE_USER='monitoring' \
  -e CLICKHOUSE_PASSWORD='change-me' \
  ghcr.io/chmonitor/chmonitor:X.Y.Z
kubectl create secret generic chmonitor-clickhouse \
  --from-literal=CLICKHOUSE_HOST='http://localhost:8123' \
  --from-literal=CLICKHOUSE_USER='monitoring' \
  --from-literal=CLICKHOUSE_PASSWORD='change-me'

Copy apps/dashboard/.env.example for a short template. Polar license checkout belongs on landing / apps/cloud-hooks, not the standalone monitor.

All variables

Search or filter every variable the dashboard reads. The list is generated from the code, so it stays current. Descriptions for each group follow below.

NameIn .env.exampleDefault
AGENTSTATE_AI_ENRICH——
AGENTSTATE_API_KEY——
AGENT_API_TOKEN——
AGENT_DEBUG——
AGENT_ENABLE_CONTROL_TOOLS——
AGENT_JSON_RENDER_PATCH_GUARD_DEBUG——
ANYROUTER_API_BASE——
ANYROUTER_API_KEYyes—
ANYROUTER_DYNAMIC_MODELS——
ANYROUTER_METRICS_CANDIDATE_CAP——
ANYROUTER_OAUTH_CLIENT_ID——
ANYROUTER_PRESETS——
ANYROUTER_PRESETS_MAX——
ANYROUTER_TOP_MODELS_N——
APP_CATEGORY——
APP_NAME——
APP_REFERER——
APP_SOURCE——
APP_VERSION——
CF_PAGES——
CHM_AGENT_FIRECRAWL_ALLOW_DOMAINSyes—
CHM_AGENT_FIRECRAWL_MCPyes—
CHM_AGENT_INTEGRATION_PROBE_TIMEOUT_MS——
CHM_AGENT_INTEGRATION_PROBE_TTL_MS——
CHM_ALLOW_PRIVATE_HOSTSyes—
CHM_ANALYTICS_HOST——
CHM_ANALYTICS_KEY——
CHM_API_KEY_SECRETyes—
CHM_AUTH_PROVIDERyes—
CHM_AUTH_REQUIRED_FEATURES——
CHM_CF_ACCESS_AUD——
CHM_CF_ACCESS_TEAM_DOMAIN——
CHM_CLERK_PUBLIC_READ——
CHM_CLOUD_MODEyes—
CHM_CONFIG_FILEyes—
CHM_DEPLOYMENT_MODEyes—
CHM_DISABLED_FEATURES——
CHM_EDITION——
CHM_EVENTS_INGEST_TOKEN——
CHM_EVENTS_REEMIT_WEBHOOK_URL——
CHM_FEATURE_CONVERSATION_DByes—
CHM_FEATURE_PEERDB_AGENT——
CHM_FEATURE_PEERDB_ENABLED——
CHM_FEATURE_POSTGRES_SOURCEyes—
CHM_FEATURE_USER_CONNECTIONS_DByes—
CHM_FEATURE_WEBHOOK_SUBSCRIPTIONS——
CHM_GUEST_AI_REQUESTS_PER_DAY——
CHM_HEALTH_SWEEP_ENABLED——
CHM_LICENSE_KEYyes—
CHM_MCP_PUBLIC——
CHM_POLAR_SERVER——
CHM_PROXY_AUTH_HEADER——
CHM_PROXY_AUTH_SECRET——
CHM_PROXY_SHARED_SECRET_HEADER——
CHM_STATE_CLICKHOUSE_DATABASE——
CHM_STATE_CLICKHOUSE_PASSWORD——
CHM_STATE_CLICKHOUSE_TABLE_PREFIX——
CHM_STATE_CLICKHOUSE_URL——
CHM_STATE_CLICKHOUSE_USER——
CHM_TABLE_MAX_RESULT_ROWSyes—
CHM_TELEMETRY_ENDPOINT——
CHM_TRUSTED_ALLOWED_GROUPS——
CHM_TRUSTED_ALLOW_INSECURE——
CHM_TRUSTED_AUTH_SECRET——
CHM_TRUSTED_AVATAR_HEADER——
CHM_TRUSTED_CUSTOM_HEADERS——
CHM_TRUSTED_EMAIL_HEADER——
CHM_TRUSTED_GROUPS_HEADER——
CHM_TRUSTED_NAME_HEADER——
CHM_TRUSTED_ROLE_HEADER——
CHM_TRUSTED_SHARED_SECRET_HEADER——
CHM_TRUSTED_USER_HEADER——
CHM_USER_CONNECTIONS_ENCRYPTION_KEY——
CHM_WEEKLY_REPORT_HOSTS——
CLERK_API_URL——
CLERK_OAUTH_ISSUER——
CLERK_PUBLISHABLE_KEY——
CLERK_SECRET_KEYyes—
CLERK_WEBHOOK_SECRET——
CLICKHOUSE_DATABASE——
CLICKHOUSE_EXCLUDE_USER_DEFAULT——
CLICKHOUSE_HOSTyeshttp://localhost:8123
CLICKHOUSE_MANAGEMENT_ENABLEDyes—
CLICKHOUSE_MAX_EXECUTION_TIME——
CLICKHOUSE_NAMEyes—
CLICKHOUSE_PASSWORDyes—
CLICKHOUSE_POOL_CLEANUP_INTERVAL——
CLICKHOUSE_POOL_SIZE——
CLICKHOUSE_POOL_TIMEOUT——
CLICKHOUSE_USERyesdefault
CLOUDFLARE_WORKERS——
CRON_SECRET——
DATABASE_URL——
EVENTS_TABLE_NAME——
GITHUB_WEBHOOK_SECRET——
HEALTH_ALERT_COOLDOWN_MINUTES——
HEALTH_ALERT_DASHBOARD_URLyes—
HEALTH_ALERT_DIGEST_MINUTES——
HEALTH_ALERT_EMAIL_ENABLED——
HEALTH_ALERT_EMAIL_FROM——
HEALTH_ALERT_EMAIL_MIN_SEVERITY——
HEALTH_ALERT_EMAIL_PROVIDER_URL——
HEALTH_ALERT_EMAIL_TO——
HEALTH_ALERT_ENABLED——
HEALTH_ALERT_HEALTHCHECKS_URL——
HEALTH_ALERT_INSTANCE_NAMEyes—
HEALTH_ALERT_METADATAyes—
HEALTH_ALERT_MIN_SEVERITY——
HEALTH_ALERT_NTFY_ENABLED——
HEALTH_ALERT_NTFY_MIN_SEVERITY——
HEALTH_ALERT_NTFY_TOKEN——
HEALTH_ALERT_NTFY_URL——
HEALTH_ALERT_OPSGENIE_API_KEY——
HEALTH_ALERT_OPSGENIE_ENABLED——
HEALTH_ALERT_OPSGENIE_MIN_SEVERITY——
HEALTH_ALERT_OPSGENIE_REGION——
HEALTH_ALERT_PAGERDUTY_API_KEY——
HEALTH_ALERT_PAGERDUTY_ENABLED——
HEALTH_ALERT_PAGERDUTY_MIN_SEVERITY——
HEALTH_ALERT_PAGERDUTY_ROUTING_KEY——
HEALTH_ALERT_PUSHOVER_ENABLED——
HEALTH_ALERT_PUSHOVER_MIN_SEVERITY——
HEALTH_ALERT_PUSHOVER_TOKEN——
HEALTH_ALERT_PUSHOVER_USER——
HEALTH_ALERT_TELEGRAM_BOT_TOKEN——
HEALTH_ALERT_TELEGRAM_CHAT_ID——
HEALTH_ALERT_TELEGRAM_ENABLED——
HEALTH_ALERT_TELEGRAM_MIN_SEVERITY——
HEALTH_ALERT_TWILIO_ACCOUNT_SID——
HEALTH_ALERT_TWILIO_AUTH_TOKEN——
HEALTH_ALERT_TWILIO_FROM——
HEALTH_ALERT_TWILIO_MIN_SEVERITY——
HEALTH_ALERT_TWILIO_TO——
HEALTH_ALERT_WEBHOOK_ENABLED——
HEALTH_ALERT_WEBHOOK_MIN_SEVERITY——
HEALTH_ALERT_WEBHOOK_TARGETS——
HEALTH_ALERT_WEBHOOK_URL——
HEALTH_HYSTERESIS_BREACHES——
HEALTH_HYSTERESIS_CLEARS——
LLM_API_BASE——
LLM_API_KEYyes—
LLM_EXTRA_MODELS——
LLM_MODELyes—
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY——
NVIDIA_API_BASE——
NVIDIA_API_KEY——
NVIDIA_DYNAMIC_MODELS——
NVIDIA_TOP_MODELS_N——
OPENROUTER_API_KEY——
OPENROUTER_APP_NAME——
OPENROUTER_DYNAMIC_MODELS——
OPENROUTER_FREE_FALLBACK_MODEL——
OPENROUTER_MODELS_API——
OPENROUTER_REFERER——
OPENROUTER_TOP_MODELS_N——
PEERDB_API_URL——
PEERDB_CACHE_MAX_ENTRIES——
PEERDB_CACHE_TTL_MS——
PEERDB_FETCH_TIMEOUT_MS——
POLAR_ACCESS_TOKEN——
POLAR_WEBHOOK_SECRET——
POSTGRES_DATABASE——
POSTGRES_HOST——
POSTGRES_NAME——
POSTGRES_PASSWORD——
POSTGRES_PORT——
POSTGRES_PRISMA_URL——
POSTGRES_SSLMODE——
POSTGRES_URL——
POSTGRES_USER——
RATE_LIMIT_AGENT_GUEST_PER_MIN——
RATE_LIMIT_AGENT_PER_MIN——
RATE_LIMIT_API_PER_MIN——
RATE_LIMIT_BROWSER_CONN_PER_MIN——
RATE_LIMIT_DEVICE_CODE_PER_MIN——
RATE_LIMIT_MCP_PER_MIN——
SLACK_CLIENT_ID——
SLACK_CLIENT_SECRET——
SLACK_OAUTH_REDIRECT_URL——
SLACK_SIGNING_SECRET——
SLACK_TOKEN_ENCRYPTION_KEY——

One canonical name per setting

A setting the browser needs (auth provider, cloud mode, feature flags) is inlined into the client bundle at build time as a VITE_* var. You do not set it twice. Set the canonical CHM_* name once and vite.config.ts derives the matching client VITE_*:

Set this (canonical)Derives this (client, build-time)
CHM_AUTH_PROVIDERVITE_AUTH_PROVIDER
CHM_CLERK_PUBLISHABLE_KEYVITE_CLERK_PUBLISHABLE_KEY
CHM_CLOUD_MODEVITE_CLOUD_MODE
CHM_FEATURE_CONVERSATION_DBVITE_FEATURE_CONVERSATION_DB
CHM_FEATURE_USER_CONNECTIONS_DBVITE_FEATURE_USER_CONNECTIONS_DB
CHM_EDITIONVITE_EDITION

Precedence per var:

Loading diagram…

The explicit VITE_* form still works as an override. Legacy NEXT_PUBLIC_* names are no longer read.

Every boolean flag (CHM_CLOUD_MODE, CHM_CLERK_PUBLIC_READ, CHM_FEATURE_*, AGENT_ENABLE_CONTROL_TOOLS, …) accepts the same values on the server and in the build: true / 1 / yes / on and false / 0 / no / off, case-insensitive. Anything else counts as unset, so the deployment-mode default applies (an unrecognised value baked into a client VITE_* flag is false). CHM_CLOUD_MODE also accepts cloud.

A dual-surface setting must still be present at both build time (so vite inlines its VITE_*) and runtime (so the server reads it) — but it is the same single name in both places.

Cloud mode is a build-time contract. CHM_CLOUD_MODE / CHM_DEPLOYMENT_MODE=cloud must be set before the build so VITE_CLOUD_MODE is inlined into the client bundle. Setting it only at runtime on a prebuilt OSS image (e.g. the published Docker image) splits the product: the server enforces cloud behaviour (demo-host guard) while the client still renders OSS UI. /api/healthz detects and warns on this mismatch and reports a cloudMode object. The reverse — a cloud build with the runtime var unset — is safe (fail-closed: both halves degrade to OSS).

Operators set CHM_* (and CLICKHOUSE_* / third-party secrets). Do not also set VITE_*. The build derives client values. Legacy NEXT_PUBLIC_* names are no longer read — see Migrate to v0.3.

Build-time vs runtime

There are two categories of environment variables:

CategoryWhen resolvedHow to setExample
Build-timeWhen pnpm run build runsCI / shell before the buildCHM_FEATURE_CONVERSATION_DB, CHM_AUTH_PROVIDER, CHM_CLERK_PUBLISHABLE_KEY
RuntimeWhen the server process starts per requestwrangler.toml [vars], wrangler secret put, Docker -e, k8s ConfigMap/SecretCLICKHOUSE_HOST, CONVERSATION_STORE_BACKEND

VITE_* variables are build-time only

Setting a VITE_* var in wrangler.toml [vars] or as a Docker -e flag has no effect on the running app — the bundle already has the value from when it was built. Always rebuild and redeploy when changing these variables.

All other vars (unless noted) are runtime — read from process.env when the Worker or Node process handles a request.

ClickHouse connection

Required for a working dashboard: host, user, password. Name is optional.

VariableRequiredDefaultPurpose
CLICKHOUSE_HOSTYes—Comma-separated ClickHouse host URLs, e.g. http://ch-1:8123,http://ch-2:8123.
CLICKHOUSE_USERYesdefaultComma-separated usernames, one per host.
CLICKHOUSE_PASSWORDYes""Comma-separated passwords, one per host.
CLICKHOUSE_NAMENo—Comma-separated display labels shown in the host selector.
CHM_ALLOW_PRIVATE_HOSTSNofalseSSRF opt-in. When true, 1, yes, on, or cloud, self-hosted installs may connect to private, loopback, link-local, and Tailscale addresses. Cloud mode forces this off.
CLICKHOUSE_MANAGEMENT_ENABLEDNooffGates CREATE USER, ALTER USER, DROP USER, GRANT, and REVOKE on /api/v1/management. Only the exact string true enables it. 1 stays off.

CLICKHOUSE_HOST defines the host count. CLICKHOUSE_USER and CLICKHOUSE_PASSWORD may be either a single value (applied to all hosts) or one value per host position. CLICKHOUSE_NAME is optional. Position N maps to host index N.

See Multiple hosts and Custom name.

Query execution

VariableDefaultPurpose
CLICKHOUSE_MAX_EXECUTION_TIME60Query timeout in seconds.
CHM_TABLE_MAX_RESULT_ROWS10000Server-side row cap for table queries (max_result_rows). 0 disables the cap. A non-numeric value also disables it.
CLICKHOUSE_DATABASEsystemDatabase used for app-owned tables (self-tracking events, dashboards).
EVENTS_TABLE_NAMEsystem.monitoring_eventsFull table name override for self-tracking events.

Connection pool

VariableDefaultPurpose
CLICKHOUSE_POOL_SIZE10Max concurrent ClickHouse clients per host.
CLICKHOUSE_POOL_TIMEOUT300000Idle client timeout in milliseconds (5 min).
CLICKHOUSE_POOL_CLEANUP_INTERVAL60000Stale-client cleanup interval in milliseconds (1 min).

Health alerting

The cron sweep (GET /api/cron/health-sweep, Cloudflare Cron Trigger every 5 minutes) runs health checks over all hosts and can post webhook alerts without a browser tab open.

VariableDefaultPurpose
CRON_SECRET—Required shared secret guarding the cron endpoints /api/cron/health-sweep and /api/cron/retention-prune. Send as Authorization: Bearer <secret> only (?secret= is rejected). When unset/empty the endpoints fail closed and return HTTP 503 (they do not run) — retention-prune is destructive, so it must not be reachable unauthenticated.
HEALTH_ALERT_ENABLEDon when any alert channel is configuredOptional master switch. Alerts dispatch as soon as a channel is configured (webhook URL, email, PagerDuty, and so on); set false (also 0, no, off) to pause all dispatch. Checks run regardless. Accepts true/1/yes/on and false/0/no/off, case-insensitive.
HEALTH_ALERT_WEBHOOK_URL—Slack- or Discord-compatible webhook URL. Setting it is enough to enable webhook alerts.
HEALTH_ALERT_MIN_SEVERITYwarningMinimum severity to alert: warning (warning + critical) or critical (critical only).
HEALTH_ALERT_WEBHOOK_ENABLEDonSet false (also 0, no, off) to turn off the webhook channel only, including the HEALTH_ALERT_WEBHOOK_URL webhook. The master HEALTH_ALERT_ENABLED still controls the whole sweep.
HEALTH_ALERT_WEBHOOK_MIN_SEVERITYinherits HEALTH_ALERT_MIN_SEVERITYSeverity floor for the webhook channel only: warning or critical. Other values are ignored.
HEALTH_ALERT_WEBHOOK_TARGETS—JSON array of extra webhook targets (up to 32). Each entry has a name, a urlEnv naming the env var that holds the URL, and optional headersEnv, enabled, format (auto, raw, slack, matrix, hookshot), minSeverity and templates. URLs and secret headers are read from the named env vars (headersEnv), never inlined; headers may hold non-secret X-* headers. Invalid JSON is ignored with no targets.
CHM_HEALTH_CONFIG_DIRECTORY/etc/chmonitor/health.dSelf-hosted only (needs a filesystem; a no-op on Cloudflare Workers). Directory of declarative alert definitions, one YAML file per concern: alerts.yaml, routing.yaml, channels.yaml, quiet-hours.yaml, maintenance.yaml, digest.yaml. Read once per process, so a changed file needs a restart. Bad files and entries are skipped with a warning, never fatal. Secret fields name an env var (secretEnv, urlEnv, headersEnv); secrets are never written in the files. Definitions merge D1/Postgres > file > env > default. Files, examples and merge rules: Run alerts without a metadata database.
CHM_HEALTH_SWEEP_ENABLEDon when CRON_SECRET is setTurns the scheduled health sweep on or off independently of the CRON_SECRET auth gate. A truthy value forces it on, a falsy value turns it off; unset or invalid falls back to on only when CRON_SECRET is configured.
HEALTH_ALERT_COOLDOWN_MINUTES60Minutes the sweep waits before re-sending an alert for a condition that stays at the same severity. 0 disables reminders (alert once until it escalates or recovers). Invalid or negative values use the default.
HEALTH_ALERT_DIGEST_MINUTES0 (off)Digest window in minutes. A positive value buffers non-critical findings and sends them as one message when the window ends; critical findings always send immediately. The in-app digest setting overrides this value.
HEALTH_ALERT_INSTANCE_NAME—Deployment label, e.g. prod-eu. Shown as a [prod-eu] prefix on every alert and digest, so alerts from several deployments sharing one channel can be told apart. Max 64 characters.
HEALTH_ALERT_DASHBOARD_URL—Public base URL of this dashboard, e.g. https://chm.example.com. Alerts link to <url>/health?host=<id>. Only http/https URLs are accepted; a trailing slash is removed.
HEALTH_ALERT_METADATA—Non-secret key/values added to alerts, as key=value,key2=value2 or a JSON object. Max 10 pairs; keys match [A-Za-z0-9_.-]{1,32}; values up to 128 characters with no line breaks. Invalid pairs are dropped. Custom webhook templates can use {{meta.<key>}}.
HEALTH_HYSTERESIS_BREACHES1Consecutive sweeps a worsening must persist before it fires. Whole numbers >= 1; anything else uses the default. A per-rule override is HEALTH_HYSTERESIS_<RULE_ID>_BREACHES (rule id uppercased, dashes to underscores).
HEALTH_HYSTERESIS_CLEARS2Consecutive ok sweeps a firing condition needs before it recovers. Same parsing as above; per-rule override HEALTH_HYSTERESIS_<RULE_ID>_CLEARS.
# Alert Slack on warning-or-worse
CRON_SECRET=a-long-random-string
HEALTH_ALERT_WEBHOOK_URL=https://hooks.slack.com/services/T000/B000/XXXX
HEALTH_ALERT_MIN_SEVERITY=warning

No metadata database? Alert definitions (rules, routing, channels, webhook targets, quiet hours, maintenance windows, digest) can be declared from CHM_HEALTH_CONFIG_DIRECTORY and the HEALTH_* variables. Alert state, ACKs and dismissals cannot: without D1 or Postgres they live in memory, reset on restart, and ACK returns 501. See the read-only/writable boundary.

Alert channels

Each channel is opt-in and only dispatches when its required variables are all set. Variables marked secret are server-side only. A channel saved from the alert settings UI overrides the matching variable here.

Every channel also takes a per-channel _ENABLED switch (false turns it off) and a _MIN_SEVERITY floor (warning or critical, defaults to HEALTH_ALERT_MIN_SEVERITY). The names are: HEALTH_ALERT_WEBHOOK_ENABLED, HEALTH_ALERT_WEBHOOK_MIN_SEVERITY, HEALTH_ALERT_EMAIL_ENABLED, HEALTH_ALERT_EMAIL_MIN_SEVERITY, HEALTH_ALERT_OPSGENIE_ENABLED, HEALTH_ALERT_OPSGENIE_MIN_SEVERITY, HEALTH_ALERT_PAGERDUTY_ENABLED, HEALTH_ALERT_PAGERDUTY_MIN_SEVERITY, HEALTH_ALERT_TELEGRAM_ENABLED, HEALTH_ALERT_TELEGRAM_MIN_SEVERITY, HEALTH_ALERT_NTFY_ENABLED, HEALTH_ALERT_NTFY_MIN_SEVERITY, HEALTH_ALERT_PUSHOVER_ENABLED, HEALTH_ALERT_PUSHOVER_MIN_SEVERITY.

VariableDefaultPurpose
HEALTH_ALERT_EMAIL_PROVIDER_URL—Secret. Email transport: mailgun://KEY@DOMAIN, sendgrid://KEY, smtp://user:pass@host:port or smtps://.... The credentials live in the URL. An unrecognised URL leaves email off.
HEALTH_ALERT_EMAIL_FROM—From address. Required for email.
HEALTH_ALERT_EMAIL_TO—Comma-separated recipients. Required for email.
HEALTH_ALERT_EMAIL_ENABLEDon once the three above are setSet false (also 0, no, off) to turn email off without removing the settings.
HEALTH_ALERT_PAGERDUTY_ENABLEDon once a PagerDuty key is setSet false (also 0, no, off) to turn PagerDuty off without removing its keys.
HEALTH_ALERT_PAGERDUTY_MIN_SEVERITYinherits HEALTH_ALERT_MIN_SEVERITYSeverity floor for PagerDuty only: warning or critical.
HEALTH_ALERT_PAGERDUTY_ROUTING_KEY—Secret. PagerDuty Events API v2 routing key, used as the fallback target when no per-rule or per-host PagerDuty route matches.
HEALTH_ALERT_PAGERDUTY_API_KEY—Secret. PagerDuty account REST API token. Used only to list services for the setup UI's picker; chmonitor never changes anything in PagerDuty with it.
HEALTH_ALERT_OPSGENIE_API_KEY—Secret. Opsgenie API key. Setting it enables Opsgenie alerts.
HEALTH_ALERT_OPSGENIE_REGIONusOpsgenie region: us or eu.
HEALTH_ALERT_TELEGRAM_BOT_TOKEN—Secret. Telegram bot token. Telegram needs both this and the chat id.
HEALTH_ALERT_TELEGRAM_CHAT_ID—Telegram chat id to post to.
HEALTH_ALERT_TELEGRAM_MIN_SEVERITYinherits HEALTH_ALERT_MIN_SEVERITYSeverity floor for Telegram only: warning or critical.
HEALTH_ALERT_NTFY_URL—Full ntfy topic URL, for example https://ntfy.sh/my-topic (a self-hosted server works). Setting it enables ntfy.
HEALTH_ALERT_NTFY_TOKEN—Secret. Optional Bearer token for a protected ntfy topic.
HEALTH_ALERT_PUSHOVER_TOKEN—Secret. Pushover application API token. Pushover needs both this and the user key.
HEALTH_ALERT_PUSHOVER_USER—Pushover user or group key to notify.
HEALTH_ALERT_TWILIO_ACCOUNT_SID—Twilio account SID. SMS needs the SID, auth token, from number and at least one recipient.
HEALTH_ALERT_TWILIO_AUTH_TOKEN—Secret. Twilio auth token.
HEALTH_ALERT_TWILIO_FROM—Twilio sender number.
HEALTH_ALERT_TWILIO_TO—Comma-separated recipient numbers (E.164). One SMS is sent per number.
HEALTH_ALERT_TWILIO_MIN_SEVERITYcriticalSeverity floor for SMS: warning or critical. SMS costs money per message, so it keeps its own floor; set warning here to also page on warnings.
HEALTH_ALERT_HEALTHCHECKS_URL—healthchecks.io ping URL pinged by the sweep.

Inbound events

VariableDefaultPurpose
CHM_EVENTS_INGEST_TOKEN—Secret. Bearer token for POST /api/events/ingest (Alertmanager, Datadog and similar webhooks). When unset the endpoint fails closed with 503; a wrong or missing token gets 401.
CHM_EVENTS_REEMIT_WEBHOOK_URL— (off)https:// URL that each ingested event is forwarded to as JSON. Best effort: URLs that fail the SSRF check and failed requests are skipped with a warning, and never break ingest.
CHM_FEATURE_WEBHOOK_SUBSCRIPTIONSmode default (true when CHM_DEPLOYMENT_MODE=cloud, else false)Per-user webhook subscriptions. Also needs CHM_AUTH_PROVIDER=clerk with CLERK_SECRET_KEY and the CHM_CLOUD_D1 binding; without them the feature stays absent.

Weekly health report

A second cron job (GET /api/cron/weekly-report) builds a proactive weekly HTML report per opted-in host — a 7-day narrative from the AI insights engine, adaptive statistical baselines, and the disk-capacity forecaster. Opt-in, never opt-out: reports are generated only for hosts listed in CHM_WEEKLY_REPORT_HOSTS.

VariableDefaultPurpose
CHM_WEEKLY_REPORT_HOSTSunset (no reports)Comma-separated host indices to opt in, e.g. 0,2. Uses the same CRON_SECRET guard as health-sweep.
# Opt host 0 and host 2 into the Monday 08:00 UTC weekly report
CRON_SECRET=a-long-random-string
CHM_WEEKLY_REPORT_HOSTS=0,2

Persisted regardless of delivery outcome; best-effort delivered to HEALTH_ALERT_WEBHOOK_URL (above) when configured. See Health — Weekly health report for the report contents and how to view a persisted report.

Prometheus metrics exporter

GET /api/v1/metrics serves system.metrics + system.asynchronous_metrics for every configured host (labeled host="<id>"), plus a chmonitor_alerts_firing gauge from the in-memory alert-dedup state, as Prometheus text exposition format. Cached ~30s; concurrent scrapes share one query batch.

VariableDefaultPurpose
CHM_FEATURE_PROMETHEUS_ENABLEDmode (self-hosted→true, cloud→false)Canonical, server-only (no client bundle exposure needed). Set explicitly to override either default. Never gated on Clerk/billing — disabled returns 404 rather than 403 so cloud never advertises the surface.
# Scrape this instance from Prometheus
CHM_FEATURE_PROMETHEUS_ENABLED=true
# prometheus.yml
scrape_configs:
  - job_name: chmonitor
    scrape_interval: 30s
    metrics_path: /api/v1/metrics
    static_configs:
      - targets: ['dash.example.com']

OTel trace export

Exports chmonitor's OWN query traces — a dashboard-request span per request, with a nested clickhouse-query span per ClickHouse call carrying query_id/read_bytes/host attributes when available — to an external OTLP collector for correlation with the rest of your tracing stack. This is DISTINCT from the built-in OTel span viewer, which reads spans already stored in system.opentelemetry_span_log.

VariableDefaultPurpose
CHM_OTEL_EXPORTER_URLunset (disabled)Collector's OTLP/HTTP traces endpoint. Must be an absolute http(s) URL, including the /v1/traces path. Unset or invalid → trace export stays off: zero spans, zero network calls, zero added latency (fail-open). Server-only, never gated on Clerk/billing.
# Export to a local Jaeger instance (OTLP/HTTP receiver)
CHM_OTEL_EXPORTER_URL=http://jaeger:4318/v1/traces

Runs on Cloudflare Workers: the exporter uses OTLP/HTTP (not gRPC, which Workers cannot run), and the batch export is flushed inline before each response returns rather than deferred with waitUntil (Workers request middleware doesn't expose it here) — so export only adds latency when explicitly enabled.

Feature permissions

All features are public and enabled by default. Override only what needs different behavior.

VariableDefaultPurpose
CHM_CONFIG_FILE—Path to a TOML (.toml) or YAML config file with a features table. Read once at server start (Node / Docker / K8s; ignored on Cloudflare Workers, which have no filesystem). Env vars below override it. Invalid entries are skipped with a warning.
CHM_DISABLED_FEATURES—Comma-separated feature ids to disable entirely.
CHM_AUTH_REQUIRED_FEATURES—Comma-separated feature ids that require authentication.
CHM_FEATURE_{ID}_ACCESSpublicPer-feature access: public, guest (alias for public), or authenticated.
CHM_FEATURE_{ID}_ENABLEDtruePer-feature enabled flag: true or false.

Replace {ID} with an uppercase feature id: OVERVIEW, AGENT, INSIGHTS, HEALTH, QUERIES, TABLES, METRICS, DASHBOARD, SECURITY, LOGS, SETTINGS, BILLING, CLUSTER, OPERATIONS, ACTIONS, MCP, DOCS, ABOUT, PEERDB.

CHM_FEATURE_AGENT_ACCESS=authenticated
CHM_FEATURE_METRICS_ENABLED=false
CHM_DISABLED_FEATURES=settings,insights

See Feature permissions for config-file examples and precedence details.

Rate limiting

Per-IP (and, for the agent, per-signed-in-identity) request throttling on the SQL-executing API surfaces. All four share one implementation (lib/api/rate-limiter.ts, checkRateLimitDurable): an in-memory token bucket everywhere, upgraded to a fleet-wide Cloudflare Rate Limiting binding on Workers deploys (CHM_RATE_LIMIT_API / CHM_RATE_LIMIT_AGENT / CHM_RATE_LIMIT_MCP / CHM_RATE_LIMIT_BROWSER_CONN in wrangler.toml) so the limit holds across isolates, not just per-isolate. Requests over the limit get 429 with a Retry-After header. None of these need to be set for the defaults to apply.

VariableDefaultPurpose
RATE_LIMIT_API_PER_MIN100GET data routes (e.g. /api/v1/charts/$name), per IP.
RATE_LIMIT_AGENT_PER_MIN10POST /api/v1/agent, per IP then tightened per signed-in identity.
RATE_LIMIT_AGENT_GUEST_PER_MIN5Cloud-only: additional per-guest identity bucket on POST /api/v1/agent (agent:guest:guest:<hash>). OSS anonymous stays IP-only.
CHM_GUEST_AI_REQUESTS_PER_DAY3Cloud-only daily AI message cap for anonymous visitors. D1 ai_usage_daily per guest:<ip-hash>. OSS is not gated.
RATE_LIMIT_MCP_PER_MIN30/api/mcp (POST/GET/DELETE), per IP. Same class of capability as the agent route — arbitrary read-only SQL via the query tool — over the MCP transport.
RATE_LIMIT_BROWSER_CONN_PER_MIN10POST /api/v1/browser-connections/{test,sessions}, per IP. Both routes are unauthenticated and dial an attacker-supplied host on every request; each route gets its own bucket so a burst against one doesn't consume the other's budget.
RATE_LIMIT_DEVICE_CODE_PER_MIN10Public CLI device-login code minting (/auth/device/code, /auth/token), per IP. Limits write load from unauthenticated callers.
CHM_TRUST_PROXY_HEADERSCloudflare runtime onlyWhen unset, X-Real-IP and X-Forwarded-For are trusted only on the Cloudflare runtime. true, 1, or cloud trust them everywhere. yes is not true here. false and 0 turn trusting off, including on Cloudflare. CF-Connecting-IP is always trusted.

Authentication

The active server-side auth provider is set by CHM_AUTH_PROVIDER. See Authentication for the full model.

VariableDefaultPurpose
CHM_AUTH_PROVIDERmode default (none; clerk when CHM_DEPLOYMENT_MODE=cloud)Server auth provider: none, clerk, proxy, or trusted. Canonical — the client VITE_AUTH_PROVIDER is derived from it at build time. Server precedence: runtime CHM_AUTH_PROVIDER → build-time VITE_AUTH_PROVIDER → runtime CHM_DEPLOYMENT_MODE default → build-time deployment-mode default. A provider set at build time is never lowered by a runtime mode. So a prebuilt image run with only CHM_DEPLOYMENT_MODE=cloud uses clerk.
VITE_AUTH_PROVIDER(from CHM_AUTH_PROVIDER; empty when unset)Client-side build-time value. Auto-derived from CHM_AUTH_PROVIDER and baked only when set; when empty the client uses the baked deployment-mode default. Only set explicitly to override.
CHM_API_KEY_SECRET—Shared secret for chm_ API keys. When set, API-key auth is always on alongside the active provider. Issue keys at /api/v1/auth/api-key. Required to enable CLI device login.
CHM_DEVICE_LOGINautoCLI device login (chm auth login / /device). auto = on in cloud when CHM_API_KEY_SECRET is set; off for self-hosted/OSS (typical internal network — mint a key once or leave the API open). true = force on (self-hosted opt-in; with CHM_AUTH_PROVIDER=none this is device-only approve — no Clerk, trust reachability of /device). false = force off. Persistence uses D1 when bound, otherwise an in-memory store (single-node).
CHM_DEVICE_LOGIN_SUBJECTself-hostedSubject embedded in chm_ tokens minted via device-only approve (auth=none). Ignored when Clerk/proxy supplies the user id.
CHM_MCP_PUBLICfalseOpen the /api/mcp endpoint with no auth. By default, when neither CHM_API_KEY_SECRET nor CLERK_SECRET_KEY is set the endpoint returns 401; set true only on a trusted private network. A warning is logged on every request while it is open.

Keep secrets server-side

CLERK_SECRET_KEY, CHM_API_KEY_SECRET, and every trust-gate secret below are runtime secrets. Never prefix them with VITE_ or NEXT_PUBLIC_, and never expose them to the browser.

Clerk (CHM_AUTH_PROVIDER=clerk)

VariableDefaultPurpose
CHM_CLERK_PUBLISHABLE_KEY—Clerk publishable key (pk_..., public). Canonical — the client VITE_CLERK_PUBLISHABLE_KEY is derived from it at build time.
VITE_CLERK_PUBLISHABLE_KEY(from CHM_CLERK_PUBLISHABLE_KEY)Client-side build-time value. Auto-derived; only set explicitly to override. Must be present before pnpm run build.
CLERK_SECRET_KEY—Clerk secret key (sk_...). Runtime only, secret. Never expose to the browser.
CLERK_PUBLISHABLE_KEY—Read only by the MCP server's OAuth metadata, as a fallback when NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY is unset: the Clerk issuer is derived from the key. The dashboard itself uses CHM_CLERK_PUBLISHABLE_KEY.
CLERK_OAUTH_ISSUERderived from the publishable keyMCP OAuth issuer URL. Set it to override the derived value, for example behind a proxied Clerk domain.
CLERK_API_URLhttps://api.clerk.comClerk API base used to verify MCP OAuth access tokens.
CHM_CLERK_PUBLIC_READmode default (false; true when CHM_DEPLOYMENT_MODE=cloud)When on, anonymous visitors can view read-only monitoring content; writes (AI agent, control actions, arbitrary SQL) still require sign-in. Runtime only. See Clerk → public read-only mode.

Reverse proxy (CHM_AUTH_PROVIDER=proxy)

Trust a reverse proxy that already authenticated the user. Either mechanism below can authenticate a request.

Cloudflare Access JWT:

VariableDefaultPurpose
CHM_CF_ACCESS_TEAM_DOMAIN—Cloudflare Access team URL (https://<team>.cloudflareaccess.com). Enables Cf-Access-Jwt-Assertion JWT verification.
CHM_CF_ACCESS_AUD—Access application AUD tag the JWT must carry.

Trusted header (bare subject):

VariableDefaultPurpose
CHM_PROXY_AUTH_SECRET—Shared secret. When set, the identity header is honored only if this secret is also present (constant-time compare). Without it, the trusted-header mechanism is disabled.
CHM_PROXY_SHARED_SECRET_HEADERX-Chm-Proxy-SecretHeader the proxy sends containing the shared secret.
CHM_PROXY_AUTH_HEADERX-Forwarded-UserHeader the proxy sends containing the authenticated user identity.

Trusted-header gate

When CHM_PROXY_AUTH_SECRET is set, the identity header (X-Forwarded-User by default) is honored only when the request also carries a matching shared-secret header (constant-time compared). Without CHM_PROXY_AUTH_SECRET, the entire trusted-header mechanism is disabled and identity headers are ignored.

Trusted reverse proxy (CHM_AUTH_PROVIDER=trusted)

Use when an upstream proxy (oauth2-proxy, Authelia, Traefik ForwardAuth, nginx auth-url) forwards the user's full identity as HTTP headers. Extracts a complete principal — name, email, avatar, groups, custom claims.

Trust gate — configure exactly one:

VariableDefaultPurpose
CHM_TRUSTED_AUTH_SECRET—Shared secret (runtime secret). The proxy must send it in CHM_TRUSTED_SHARED_SECRET_HEADER. Constant-time compared. Recommended.
CHM_TRUSTED_ALLOW_INSECUREfalseWhen true, trusts headers with no secret check. Only safe when the service is unreachable except via the proxy (e.g. k8s ClusterIP).
CHM_TRUSTED_SHARED_SECRET_HEADERX-Chm-Proxy-SecretHeader name carrying the shared secret.

Identity headers — all optional; defaults match common proxy conventions:

VariableDefaultPopulates
CHM_TRUSTED_USER_HEADERX-Forwarded-UserSubject / user id. Falls back to the email header.
CHM_TRUSTED_EMAIL_HEADERX-Forwarded-EmailEmail address.
CHM_TRUSTED_NAME_HEADERX-Forwarded-Preferred-UsernameDisplay name.
CHM_TRUSTED_AVATAR_HEADERX-Forwarded-AvatarAvatar URL.
CHM_TRUSTED_GROUPS_HEADERX-Forwarded-GroupsComma- or space-separated groups.
CHM_TRUSTED_ROLE_HEADERX-Forwarded-RoleSingle role; merged into the groups list.
CHM_TRUSTED_CUSTOM_HEADERS—Extra claims. Comma-separated field:Header-Name pairs, e.g. team:X-Forwarded-Team.

Access control:

VariableDefaultPurpose
CHM_TRUSTED_ALLOWED_GROUPS—Comma-separated group names. When set, users whose forwarded groups do not intersect are denied 403. Case-insensitive.

See Trusted proxy for setup examples (oauth2-proxy + Dex + Traefik, nginx).

AI agent / LLM provider

The agent uses an OpenAI-compatible API. Set LLM_API_KEY to enable it. Keep all LLM keys server-side — never prefix them with VITE_.

VariableDefaultPurpose
LLM_API_KEY—Provider API key (fallback when no provider-specific key is set).
LLM_API_BASEhttps://openrouter.ai/api/v1OpenAI-compatible API base URL.
LLM_MODELopenrouter:openrouter/freeModel identifier in provider:modelId form.
LLM_EXTRA_MODELS—Additional model picker entries. Format: provider:modelId[|contextLength][|description], comma-separated. Providers: openrouter, nvidia, anyrouter.

Provider-specific keys

Provider-specific keys take priority over the generic LLM_API_KEY / LLM_API_BASE.

VariablePurpose
OPENROUTER_API_KEYOpenRouter API key.
OPENROUTER_API_BASEOpenRouter base URL.
OPENROUTER_REFERERHTTP referer sent to OpenRouter (for rankings).
OPENROUTER_APP_NAMEApp name sent to OpenRouter (for rankings).
OPENROUTER_MODELS_APIOpenRouter models list endpoint (default https://openrouter.ai/api/v1/models).
NVIDIA_API_KEYNVIDIA NIM API key.
NVIDIA_API_BASENVIDIA NIM base URL.
ANYROUTER_API_KEYAnyRouter API key.
ANYROUTER_API_BASEAnyRouter base URL.
ANYROUTER_PRESETS / ANYROUTER_PRESETS_MAXSurface workspace presets in the model picker (on by default, max 8).
ANYROUTER_OAUTH_CLIENT_IDPre-registered OAuth client for "Sign in with AnyRouter" (optional).
OPENROUTER_DYNAMIC_MODELS / OPENROUTER_TOP_MODELS_NLive OpenRouter catalog enrichment (on by default, top 12).
OPENROUTER_FREE_FALLBACK_MODELConcrete model that openrouter/free resolves to (default google/gemma-4-31b-it:free). Override it if the default is delisted upstream.
NVIDIA_DYNAMIC_MODELS / NVIDIA_TOP_MODELS_NLive NVIDIA NIM catalog enrichment (on by default, top 12).
ANYROUTER_DYNAMIC_MODELS / ANYROUTER_TOP_MODELS_N / ANYROUTER_METRICS_CANDIDATE_CAPLive AnyRouter catalog enrichment (on by default, needs the AnyRouter key; set the first to false, 0, no or off to disable). Top 8 (max 32) and metrics candidate cap 24 (max 64).

Agent behavior

VariableDefaultPurpose
AGENT_API_TOKEN—Shared Bearer token for the agent API (POST /api/v1/agent). Accepted when CHM_FEATURE_AGENT_ACCESS=authenticated.
AGENT_ENABLE_CONTROL_TOOLSon for self-hosted, off in cloudGives the agent kill_query, optimize_table, and kill_mutation. Unset: on in self-hosted when auth is none or the caller is signed in, off for anonymous Clerk visitors and in cloud mode. false turns them off everywhere; true turns them on, but anonymous cloud visitors never get them. The actions feature permission still applies.
CHM_AGENT_FIRECRAWL_MCPtrueConnect Firecrawl's hosted MCP (no API key) on every agent request. Set false to disable.
CHM_AGENT_FIRECRAWL_ALLOW_DOMAINS(empty)Comma-separated hostnames Firecrawl may scrape/crawl/map. Empty / unset = unrestricted. Apex tokens also match subdomains; *.example.com is subdomain-only (not the apex). Search-only calls (no URL) stay allowed.
AGENT_DEBUGfalseVerbose agent request and usage logging (message keys, resolved user ids, usage). Accepts 1, true, yes, on. Off unless set explicitly, whatever NODE_ENV is.
AGENT_JSON_RENDER_PATCH_GUARD_DEBUG—Set 1 to log each rendered-spec chunk the patch guard drops. Ignored when NODE_ENV=production.

Attribution headers

Sent to the LLM gateway so usage is attributed to this app. All are optional.

VariableDefaultPurpose
APP_REFEREROPENROUTER_REFERER, else https://chmonitor.devHTTP-Referer header.
APP_NAMEOPENROUTER_APP_NAME, else chmonitorApp title header.
APP_SOURCEchmonitorApp identifier AnyRouter groups usage by.
APP_CATEGORYprogramming-appMarketplace category header.
APP_VERSION0.2.0App version sent with the attribution metadata. On Cloudflare Workers it also overrides the ui version returned by GET /api/version.

See AI agent — configuration for full details.

Conversation store

Agent conversations default to browser localStorage. Enable server-side persistence with two steps:

Enable the feature (build-time)

Set CHM_FEATURE_CONVERSATION_DB=true (the client VITE_FEATURE_CONVERSATION_DB is derived from it at build time — must be present before pnpm run build). Also requires Clerk auth (CHM_AUTH_PROVIDER=clerk).

Choose a backend (runtime)

Configure a backend at runtime via CONVERSATION_STORE_BACKEND.

VariableDefaultPurpose
CHM_FEATURE_CONVERSATION_DBfalseCanonical. Set true to enable server-side conversation persistence. Requires Clerk. Drives the build-time VITE_FEATURE_CONVERSATION_DB.
VITE_FEATURE_CONVERSATION_DB(from CHM_FEATURE_CONVERSATION_DB)Client-side build-time value. Auto-derived; only set explicitly to override. Must be present before pnpm run build.
CONVERSATION_STORE_BACKEND(auto)Runtime. Backend: agentstate, d1, postgres, or memory. When unset, the server auto-selects the first available backend (AgentState if key present → D1 binding → Postgres via DATABASE_URL → Memory).

AgentState backend:

VariableDefaultPurpose
AGENTSTATE_API_KEY—AgentState API key. Required when CONVERSATION_STORE_BACKEND=agentstate.
AGENTSTATE_BASE_URLhttps://agentstate.app/apiAgentState API base URL.
AGENTSTATE_AI_ENRICHfalse (cloud production: true)Set true to enable AI titles and follow-up suggestions.

Cloudflare D1 backend:

VariableDefaultPurpose
CHM_CLOUD_D1_DATABASE_ID—Cloudflare D1 database UUID for the CHM_CLOUD_D1 binding.
AGENT_CHM_CLOUD_D1_DATABASE_ID—Alias for CHM_CLOUD_D1_DATABASE_ID.

PostgreSQL backend:

VariablePurpose
DATABASE_URLPostgreSQL connection string.
POSTGRES_URLAlternative PostgreSQL connection string.
POSTGRES_PRISMA_URLPrisma-specific PostgreSQL connection string.

The same URL also backs the alert settings stores (routing, channels, custom rules, webhook targets, acks, quiet hours, maintenance windows, digests, alert state and history) when no D1 binding is present. Their tables are created on first use (CREATE TABLE IF NOT EXISTS); there is no migration step.

See Conversation history — backends for setup and fallback behavior.

ClickHouse state backend (self-hosted):

Persists dashboards and per-user connections in your own ClickHouse instead of Cloudflare D1 or Postgres. Opt-in; tables are created lazily (CREATE TABLE IF NOT EXISTS, ReplacingMergeTree). Resolution order per store: D1 binding → ClickHouse state env → Postgres env → local/memory. Alert settings have no ClickHouse store: they use D1, then Postgres, and are unavailable on a ClickHouse-only state backend.

VariableDefaultPurpose
CHM_STATE_CLICKHOUSE_URL—ClickHouse HTTP(S) URL for state storage. Setting it enables the backend.
CHM_STATE_CLICKHOUSE_USERdefaultClickHouse user (needs CREATE/INSERT/SELECT/DELETE on the state database).
CHM_STATE_CLICKHOUSE_PASSWORD(empty)ClickHouse password (secret).
CHM_STATE_CLICKHOUSE_DATABASEchmonitorDatabase holding the state tables.
CHM_STATE_CLICKHOUSE_TABLE_PREFIXchm_state_Prefix for the state tables (chm_state_dashboards, chm_state_user_connections).

See Self-hosted state backends for setup.

User connections (server storage)

Let signed-in users save personal ClickHouse hosts to the server. All four of these must be present together, or the server returns "User connections database storage is not enabled".

VariableDefaultPurpose
CHM_FEATURE_USER_CONNECTIONS_DBfalseCanonical. Set true to enable. Drives the build-time VITE_FEATURE_USER_CONNECTIONS_DB.
VITE_FEATURE_USER_CONNECTIONS_DB(from CHM_FEATURE_USER_CONNECTIONS_DB)Client-side build-time value. Auto-derived; only set explicitly to override.
CHM_AUTH_PROVIDERnoneMust be clerk — connections are keyed by the Clerk userId.
CHM_USER_CONNECTIONS_ENCRYPTION_KEY—32-byte base64 key (secret) encrypting saved credentials at rest (AES-256-GCM).

Plus a database backend: the CHM_CLOUD_D1 binding on Cloudflare, or DATABASE_URL / POSTGRES_URL on Docker / Kubernetes.

See User connections for the full setup.

AI insights store

The AI Insights panel on /overview persists its findings through a pluggable store. It is additive opt-in and defaults to ClickHouse; the D1, Postgres, and AgentState backends reuse the conversation-store env above.

VariableDefaultPurpose
INSIGHTS_STORE_BACKENDautoBackend: auto, clickhouse, d1, postgres, agentstate, or memory. auto and clickhouse both use the ClickHouse monitoring_findings table. auto never silently follows other env; a selected backend missing its prerequisite falls back to ClickHouse.
INSIGHTS_D1—Optional dedicated D1 binding for the d1 backend. Falls back to CHM_CLOUD_D1 when unset.

postgres reuses DATABASE_URL; agentstate reuses AGENTSTATE_API_KEY (+ optional AGENTSTATE_BASE_URL).

See AI agent — AI Insights persistence for setup and fallback behavior. GET /api/v1/insights/backend reports the active backend.

PeerDB monitoring

Optional. Set PEERDB_API_URL to enable the PeerDB section (Mirrors and Peers) in the sidebar.

VariableDefaultPurpose
PEERDB_API_URL—PeerDB REST API base URL. For the PeerDB UI (NextAuth), include /api suffix — e.g. https://peerdb.example.com/api. For the raw flow-api, use the bare origin — e.g. http://localhost:8113.
PEERDB_PASSWORD—PeerDB password. Sent as HTTP Basic with an empty username. Server-side only.
PEERDB_CACHE_TTL_MS10000Server-side response cache TTL in milliseconds. Set 0 to disable.
PEERDB_CACHE_MAX_ENTRIES500Max cached PeerDB responses before oldest are evicted.
PEERDB_FETCH_TIMEOUT_MS10000Upstream PeerDB request timeout in milliseconds.
PEERDB_SWEEP_CONCURRENCY8Max in-flight PeerDB reads per alert-sweep tick.
PEERDB_SWEEP_BUDGET_MS60000Wall-clock budget for one alert-sweep collection in milliseconds.
PEERDB_SWEEP_MAX_MIRRORSunset (no limit)Optional cap on mirrors read per alert-sweep tick. Unset means every listed mirror is read; any truncation is recorded as a partial result.
CHM_FEATURE_PEERDB_ENABLEDunset (enabled)Set false to disable PeerDB entirely: hides the section, blocks the proxy, and skips the PeerDB alert cycle in the health sweep.
CHM_FEATURE_PEERDB_AGENTunset (auto)The AI agent's read-only PeerDB tools. Unset: on when PEERDB_API_URL is set and PeerDB answers a quick probe. true: on whenever PEERDB_API_URL is set. false: off. Always off for anonymous cloud visitors; signed-in cloud users and requests on a user's own connection need an explicit true. CHM_FEATURE_PEERDB_ENABLED=false also turns it off.
CHM_AGENT_INTEGRATION_PROBE_TIMEOUT_MS1500How long the agent waits for the PeerDB / Postgres reachability probe before treating the integration as unreachable for that conversation. Can lower the default, never raise it. The probe keeps running in the background and fills the cache for the next conversation.
CHM_AGENT_INTEGRATION_PROBE_TTL_MS60000How long a successful probe result is reused per server instance. Failed results are reused for at most 15 seconds.

chmonitor proxies only a read-only allowlist of PeerDB endpoints; mutating operations (create/drop/pause, alert config) are rejected with 403.

# PeerDB UI behind NextAuth
PEERDB_API_URL=https://peerdb.example.com/api
PEERDB_PASSWORD=your-peerdb-ui-password

# Raw flow-api (no auth)
PEERDB_API_URL=http://localhost:8113

Postgres source

Optional read-only Postgres monitoring source, off by default. Each list is comma-separated and aligned by position with POSTGRES_HOST, so a single value for POSTGRES_USER or POSTGRES_PASSWORD applies to every host.

VariableDefaultPurpose
CHM_FEATURE_POSTGRES_SOURCEunsetTurn on the Postgres source engine and its API routes (true; the UI and routes stay off when unset). For the agent's read-only cross-source tools it is three-state: unset turns them on when a POSTGRES_HOST is set and answers SELECT 1 within the probe budget; true turns them on whenever a host is set; false turns them off. Always off for anonymous cloud visitors; signed-in cloud users and requests on a user's own connection need an explicit true.
POSTGRES_HOST—Host list, each host or host:port. Defines which sources exist; with none set the list is empty.
POSTGRES_PORT5432Port list. A port inside a POSTGRES_HOST entry wins; a missing entry falls back to the first value.
POSTGRES_USERpostgresUser list.
POSTGRES_PASSWORD—Secret. Password list.
POSTGRES_DATABASEpostgresDatabase list. A missing entry falls back to the first value.
POSTGRES_SSLMODErequirelibpq sslmode list: disable, require, verify-ca, verify-full. prefer and allow behave like require. require encrypts without verifying the certificate.
POSTGRES_NAME—Display name list.

Slack app

Optional native Slack app (OAuth install, /chmonitor slash command, Home tab, ACK buttons). With any of the first three variables missing the feature is off and its routes return "not configured". Set the secrets as Worker secrets or in .env.local, never in committed files.

VariableDefaultPurpose
SLACK_CLIENT_ID—Slack app OAuth client id (public).
SLACK_CLIENT_SECRET—Secret. OAuth client secret, used to exchange the install code for a token.
SLACK_SIGNING_SECRET—Secret. Verifies the signature of every inbound Slack request. Also the key source for token encryption when no dedicated key is set.
SLACK_OAUTH_REDIRECT_URLrequest origin + /api/v1/slack/oauthOAuth redirect URI. Set it to match the URL registered in the Slack app exactly.
SLACK_TOKEN_ENCRYPTION_KEYderived from SLACK_SIGNING_SECRETSecret. Optional dedicated 32-byte base64 key for encrypting stored bot tokens, so it can rotate separately from the signing secret. Any other length is rejected.

GitHub deployment webhook

VariableDefaultPurpose
GITHUB_WEBHOOK_SECRET—Secret. Signing secret for inbound GitHub deployment and deployment_status webhooks. One global secret. When unset the route returns 501 and ingests nothing; a missing or wrong signature gets 401.

Analytics & branding

Set the canonical CHM_* names. Client VITE_* is derived at build time.

VariableDefaultPurpose
CHM_ANALYTICS_KEY—PostHog project key for opt-in Product Analytics. Empty = disabled.
CHM_ANALYTICS_HOSThttps://us.i.posthog.comPostHog ingest host.

Build-time client override (optional):

VariableDefaultPurpose
VITE_AUTOCOMPLETE_LIMIT—Max rows the SQL autocomplete loader requests. Set at build time.

CLI client

Env vars for the standalone chm / chmonitor CLI (crate chmonitor). These are read by the binary and scripts/install.sh, not by the dashboard server. Flags override env; env overrides ./chm.toml / ~/.config/chm/config.toml. Default API base is https://dash.chmonitor.dev.

chm auth login discovers how to authenticate by probing public GET /api/v1/auth/cli (method: none | api_key | device) — there is no auth_mode in config. See API keys — device login.

VariableDefaultPurpose
CHM_BASE_URLhttps://dash.chmonitor.devDashboard API origin for hosts / chart / table / tui / auth / …
CHM_CHANNELstableRelease channel for install.sh and chm update: stable or beta. Also --channel / config channel. chm update --beta installs from beta and writes channel = "beta" to the user config (--stable writes stable).
CHM_API_KEY—chm_ API key (also --api-key). Used when discovery returns api_key, or alongside device login.
CHM_TOKEN—Bearer token from device login (also --token). Usually stored in the OS keyring.
CHM_HOST_ID0Host index for dashboard API calls (?host=).
CHM_CONFIG—Path to a config TOML (overrides the default search paths).
CHM_NO_UPDATE_CHECK—Set to 1 to silence the post-command “update available” hint.
CHM_TELEMETRYonSame as dashboard: off / 0 / false / no disables CLI telemetry.
CHM_TELEMETRY_ENDPOINThttps://telemetry.chmonitor.dev/v1/pingEmpty string = hard kill-switch.
DO_NOT_TRACK—Cross-tool hard override (1) disables CLI telemetry.

Installer (scripts/install.sh)

VariableDefaultPurpose
CHM_CHANNELstablestable skips prereleases; beta prefers chm-v*-beta.N.
CHM_VERSIONlatest for channelPin a release tag (chm-vX.Y.Z, vX.Y.Z, or X.Y.Z).
CHM_INSTALL_DIR$HOME/.local/binInstall directory (never uses sudo).

Server-side device login gates (CHM_DEVICE_LOGIN, CHM_API_KEY_SECRET, …) live under Authentication.

Product telemetry

Anonymous product telemetry is on by default. See Product telemetry.

VariableDefaultPurpose
CHM_TELEMETRYonSet to off (also 0 / false / no) to disable. DO_NOT_TRACK=1 is a hard override.
CHM_TELEMETRY_ENDPOINThttps://telemetry.chmonitor.dev/v1/pingCollection endpoint. Empty string = hard kill-switch (no network).
CHM_LICENSE_KEY—Optional. Polar checkout id from a commercial license receipt (the same id lookup accepts). Included on the daily instance ping when telemetry is on. Does not unlock features or fail startup. Do not put a billing email here.

Polar licenses (landing / cloud-hooks)

Self-host license checkout is not configured on the dashboard example. Set Polar product ids and secrets on apps/cloud-hooks (and the hosted .env.production catalog). See apps/cloud-hooks/.env.example. Operators who already bought a license set CHM_LICENSE_KEY on the dashboard process (see Product telemetry above) — not on cloud-hooks.

VariablePurpose
CHM_POLAR_SERVERsandbox or production.
CHM_POLAR_LICENSE_*License SKU product ids (team/unlimited × yearly/lifetime).
POLAR_ACCESS_TOKENOrg access token (secret).
POLAR_WEBHOOK_SECRETWebhook signing secret.

Ops notifications worker (cloud-hooks)

apps/cloud-hooks is the Cloudflare Worker for Polar license checkout, Clerk lifecycle hooks, health-probe digests, and exception/issue-watch notifications. Bindings (CHM_CLOUD_D1, CHM_TELEMETRY_DB, CHM_HOOKS_KV) are declared in apps/cloud-hooks/wrangler.toml, not here. See apps/cloud-hooks/.env.example.

VariablePurpose
POLAR_WEBHOOK_SECRETPolar webhook HMAC signing secret.
POLAR_ACCESS_TOKENPolar org API token for checkout/re-key.
CLERK_SECRET_KEYClerk backend API key (when Clerk hooks are enabled).
CLERK_WEBHOOK_SECRETClerk webhook signing secret (whsec_…).
TELEGRAM_BOT_TOKENTelegram bot token for ops digests/alerts.
TELEGRAM_CHAT_IDDestination chat/channel id for Telegram alerts.
GITHUB_TOKENPAT with issues:write (fallback when GitHub App creds unset).
GH_APP_IDGitHub App id for exception/issue filing (preferred over PAT).
GH_APP_PRIVATE_KEYGitHub App PKCS#8 private key PEM.
GH_APP_INSTALLATION_IDOptional fixed installation id (else resolved from repo).
CF_OBSERVABILITY_API_TOKENCloudflare API token with Workers Observability read.
CHM_POLAR_SERVERsandbox or production Polar API host.
CHM_LICENSE_SUCCESS_ORIGINPost-checkout redirect origin (default https://chmonitor.dev).
CF_ACCOUNT_IDCloudflare account id for observability queries.
GITHUB_REPOSITORYowner/repo for filed issues (default chmonitor/chmonitor).
CHM_EXCEPTION_ISSUE_LABELSComma-separated labels for exception issues.
CHM_EXCEPTION_MAX_ISSUES_PER_RUNMax new exception issues per scan (default 5).
CHM_EXCEPTION_SCRIPTSComma-separated Worker script names to scan.
CHM_ISSUE_WATCH_EXCLUDE_LABELSLabels whose new issues the watch stays quiet about.
CHM_ISSUE_WATCH_MAX_PER_RUNMax new issues announced per ops sweep (default 10).
CHM_POLAR_LICENSE_TEAM_YEARLYPolar product id — team yearly license.
CHM_POLAR_LICENSE_TEAM_LIFETIMEPolar product id — team lifetime license.
CHM_POLAR_LICENSE_UNLIMITED_YEARLYPolar product id — unlimited yearly license.
CHM_POLAR_LICENSE_UNLIMITED_LIFETIMEPolar product id — unlimited lifetime license.

Runtime & build

VariableDefaultPurpose
NODE_ENVdevelopmentRuntime environment: development, production, test.
ENABLE_CLOUDFLAREfalseEnable Cloudflare-specific build configuration.
CLOUDFLARE_WORKERS—Set to 1 when running on Cloudflare Workers.
CF_PAGES—Set automatically by Cloudflare Pages at runtime.
MINIFLARE—Set to 1 when running locally with Miniflare.
DOCS_CONTENT_ROOT—Override docs content source directory.

Build-time only

Injected at build time for the About page.

VariablePurpose
VITE_GIT_SHACurrent commit SHA.
VITE_GIT_REFCurrent git branch or tag.
VITE_BUILD_TIMESTAMPISO build timestamp.
VITE_CISet to true in CI environments.

Query config source

VariableDefaultPurpose
CHM_CONFIG_SOURCEtsQuery-config resolution path: ts (hand-written TypeScript configs, current default everywhere) or declarative (opt-in data-only catalog, validated against the same schema and kept at parity — see Catalog contributing). Any value other than the literal string declarative resolves to ts. A malformed declarative entry logs an error and falls back to the ts config rather than crashing.
CHM_CONFIG_DIRECTORY/etc/chmonitor/queries.dSelf-hosted only. Directory scanned once at startup for *.yaml local query-config overrides — no PR or fork needed. Each file must satisfy the same declarative schema as the catalog; an invalid file (bad YAML, schema violation, or duplicate name) is skipped with a warning log, never crashes boot. Checked first, ahead of CHM_CONFIG_SOURCE — a local file overrides a same-named built-in ts or declarative config regardless of that setting. Requires a real filesystem (Docker/K8s; not available on the Cloudflare Worker deploy). Mount example in the repo's docker-compose.yml.
CHM_PACK_REGISTRY_URL—Comma-separated community query-pack URLs (HTTP(S) or file://), consulted when CHM_CONFIG_SOURCE=declarative. Each pack is a single YAML manifest ({ name, version, queries: [...] }) fetched once at startup (no hot-reload). HTTP(S) fetches are SSRF-guarded; file:// reads a local, operator-chosen path. Precedence (highest to lowest): the self-hosted local queries.d override > packs > the built-in declarative catalog > ts configs. Within packs, the last URL wins on a name collision (logged). An unreachable or malformed pack — or a single bad query inside an otherwise-valid pack — is skipped with a warning; the built-in catalog always still serves.

Legacy / migration

VariableNotes
NEXT_PUBLIC_AUTH_PROVIDERRemoved. Set CHM_AUTH_PROVIDER (renamed to VITE_AUTH_PROVIDER in v0.3; the old name is no longer read).
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEYRemoved. Set CHM_CLERK_PUBLISHABLE_KEY (the old name is no longer read).
NEXT_PUBLIC_FEATURE_CONVERSATION_DBRemoved. Set CHM_FEATURE_CONVERSATION_DB (the old name is no longer read).
NEXT_PUBLIC_AUTOCOMPLETE_LIMITRemoved. Set VITE_AUTOCOMPLETE_LIMIT (the old name is no longer read).
NEXT_PUBLIC_RUNNING_QUERIES_REFRESH_MSRemoved. Set VITE_RUNNING_QUERIES_REFRESH_MS (the old name is no longer read).
CLICKHOUSE_EXCLUDE_USER_DEFAULTComma-separated usernames excluded from history-queries by default.

See Migrate to v0.3 for the full rename list.

On this page