Configuration Reference
How configuration is resolved
Environment variables are the primary configuration surface for self-hosted TokenTimer. Defaults come from code fallbacks, then from Compose or Helm example values where applicable.
Some settings can also be stored in the database through the System Settings admin UI. When both exist, the precedence differs per setting:
| Setting | Precedence (highest wins) |
|---|---|
| SMTP config | System Settings DB > env vars > code defaults |
| Twilio credentials and template SIDs | System Settings DB > env vars > code defaults |
| Alert thresholds, delivery window | Workspace preferences DB > env vars > code defaults |
CERTOPS_ENABLED | Env var > System Settings DB > default (false) |
| Admin bootstrap | Env vars only |
In the tables below, unset means optional and disabled unless you set it. required means you must provide a value for production.
Core runtime and bootstrap
| Variable | Description | Default value | Scope |
|---|---|---|---|
TT_MODE | Core variant mode | oss | App mode |
NODE_ENV | Runtime environment | development | API, worker, dashboard |
SESSION_SECRET | Session signing secret | required | API, worker |
ADMIN_EMAIL | First admin bootstrap email | admin@your-company.com | API bootstrap |
ADMIN_PASSWORD | First admin bootstrap password | ChangeThisSecurePassword123! | API bootstrap |
ADMIN_NAME | First admin bootstrap display name | Administrator | API bootstrap |
DISABLE_ADMIN_BOOTSTRAP | Skip first admin auto-create when true | false | API bootstrap |
PORT | API listen port | 4000 | API |
HOST | API bind host | 0.0.0.0 | API |
API_PORT | Compose API port mapping | 4000 | Local compose |
DASHBOARD_PORT | Compose dashboard port mapping | 5173 | Local compose |
See Authentication for the full admin bootstrap flow.
Canonical URLs and API docs
| Variable | Description | Default value | Scope |
|---|---|---|---|
APP_URL | Frontend public URL (canonical) | http://localhost:5173 | API, worker, dashboard |
API_URL | Backend public URL (canonical) | http://localhost:4000 | API, worker, dashboard |
ENABLE_API_DOCS | Enable Swagger UI (/api-docs) in production | false | API docs serving |
VITE_API_URL | Frontend build-time API override | http://localhost:4000 | Dashboard build |
OPENAPI_SPEC_PATH | Optional absolute path to OpenAPI YAML | unset (auto-discovery) | API docs serving |
PUBLIC_BASE_URL | Public URL used for webhook signature validation | unset (falls back to API_URL) | API webhook auth |
Helm vs Compose vs runtime
| Layer | APP_URL | API_URL when unset in your config |
|---|---|---|
Helm (config.baseUrl / apiUrl) | baseUrl | apiUrl if set, else baseUrl (same host) |
Helm stock values.yaml | http://localhost:8080 | http://localhost:4000 (both set explicitly) |
Docker Compose (.env) | ${APP_URL:-http://localhost:5173} | ${API_URL:-http://localhost:4000} (no cross-fallback in compose files) |
| API process (env missing) | http://localhost:5173 | API_URL, else APP_URL, else http://localhost:4000 |
In local http://localhost with NODE_ENV=production, the browser does not persist or send the secure session cookie for the auth flow. Use HTTPS in front of the API and dashboard (reverse proxy with TLS), otherwise secure cookies are expected to fail in local HTTP.
Split-host deployments (dashboard and API on different origins)
When APP_URL and API_URL differ on HTTPS (for example dashboard at https://app.example.com and API at https://api.example.com), the API:
- Allows both origins in CORS (configured
APP_URLandAPI_URLonly in production; localhost dev origins are omitted unlessALLOW_LOCAL_DEV_CORS=true). - Sets session and CSRF cookies to
SameSite=None; Secureon the API host. Host-only cookies (noSESSION_COOKIE_DOMAIN) are enough for credentialedwithCredentialscalls from the dashboard toapi.example.com. SetSESSION_COOKIE_DOMAINonly when you intentionally need a parent-domain cookie shared across multiple subdomains (broader scope; weaker isolation).
HTTP split-host (including LAN IPs or internal DNS without TLS) does not use SameSite=None; cookies stay Lax because None requires Secure and is often blocked. Use HTTPS for split-host production, or put UI and API behind one origin (ingress).
Docker Compose (http://localhost:5173 + http://localhost:4000) and Helm port-forward (http://localhost:8080 + http://localhost:4000) keep SameSite=Lax (same-site across ports on local HTTP). For plain HTTP + NODE_ENV=production, set SESSION_COOKIE_SECURE_LOCALHOST_OVERRIDE=true only when both APP_URL and API_URL are local HTTP (localhost / 127.0.0.1); the flag is ignored on internet-facing HTTPS URLs.
Same-host installs (single ingress hostname for UI and API) can leave SESSION_COOKIE_DOMAIN unset. Helm defaults often use baseUrl: http://localhost:8080 and apiUrl: http://localhost:4000; Compose maps the dashboard to host port 5173 by default (APP_URL=http://localhost:5173). Always set APP_URL and API_URL to the origins users and integrations actually use in the browser.
Database and pooling
| Variable | Description | Default value | Scope |
|---|---|---|---|
DB_HOST | PostgreSQL host | localhost (code), postgres (compose) | API, worker |
DB_PORT | PostgreSQL port | 5432 | API, worker |
DB_NAME | PostgreSQL database name | tokentimer | API, worker |
DB_USER | PostgreSQL user | tokentimer | API, worker |
DB_PASSWORD | PostgreSQL password | password (dev fallback), required (prod) | API, worker |
DATABASE_URL | Full PostgreSQL connection URL override | unset | API, worker |
DB_SSL | DB SSL mode (require or verify) | unset (disabled) | API, worker |
PGSSLROOTCERT | Path to CA cert for DB SSL verify mode | unset | API, worker |
DB_POOL_MAX | Max DB pool connections | 10 | API, worker |
DB_POOL_MIN | Min DB pool connections | 2 | API, worker |
DB_POOL_IDLE_TIMEOUT | Idle connection timeout (ms) | 30000 | API, worker |
DB_CONNECTION_TIMEOUT | DB connection timeout (ms) | 5000 | API, worker |
PostgreSQL is the only stateful component. See Backup and restore for backup guidance.
Authentication and security
| Variable | Description | Default value | Scope |
|---|---|---|---|
LOCAL_AUTH_ENABLED | Enable local email/password auth | true | API auth |
REQUIRE_EMAIL_VERIFICATION | Require verified email before access | true | API auth |
TWO_FACTOR_ENABLED | Enable 2FA features | true | API auth |
SESSION_MAX_AGE | Session max age in ms | 86400000 | API auth |
SESSION_COOKIE_SECURE_LOCALHOST_OVERRIDE | Allow insecure session cookies in production only when APP_URL and API_URL are both local HTTP (localhost / 127.0.0.1); ignored otherwise | false | API auth |
SESSION_COOKIE_DOMAIN | Optional parent domain for session/CSRF cookies (e.g. .example.com) when you need cookies shared across subdomains; not required for typical split-host API calls | unset | API auth |
ALLOW_LOCAL_DEV_CORS | In production, also allow http://localhost:* and http://127.0.0.1:* in CORS (local troubleshooting only) | false | API security |
CSRF_ENABLED | Enable CSRF protection | true | API security |
MIN_PASSWORD_LENGTH | Minimum password length | 8 | API auth |
REQUIRE_UPPERCASE | Enforce uppercase in passwords | true | API auth |
REQUIRE_NUMBERS | Enforce numeric chars in passwords | true | API auth |
PHONE_HASH_SALT | Optional salt for phone hashing | unset | API privacy |
TRUST_PROXY_HOPS | Number of trusted reverse-proxy hops in front of the API (affects req.ip and req.protocol resolution). 0 = no proxy, 1 = single ingress/reverse proxy, 2 = LB -> ingress. | 2 | API security |
WORKER_API_KEY | Worker-to-API auth key | unset (falls back to SESSION_SECRET) | Worker, API |
Email and delivery (SMTP)
| Variable | Description | Default value | Scope |
|---|---|---|---|
SMTP_HOST | SMTP host(s) (comma-separated) | localhost in config helper, unset in compose examples | API, worker email |
SMTP_PORT | SMTP port(s) (comma-separated) | 587 in config helper | API, worker email |
SMTP_USER | SMTP username(s) (comma-separated) | unset | API, worker email |
SMTP_PASS | SMTP password(s) (comma-separated) | unset | API, worker email |
FROM_EMAIL | Sender email override | unset | API, worker email |
FROM_EMAIL_NAME | Sender display name | TokenTimer | API, worker email |
SMTP_SECURE | Force SMTPS/SSL | false | API, worker email |
SMTP_REQUIRE_TLS | Require STARTTLS upgrade | true | API, worker email |
SMTP_REJECT_UNAUTHORIZED | Reject invalid TLS certs | unset | API, worker email |
SMTP can be configured via env vars or from the System Settings admin UI. Env vars serve as bootstrap values; once an admin configures SMTP in the UI, the database values take precedence.
Alerts, limits, and webhooks
| Variable | Description | Default value | Scope |
|---|---|---|---|
ALERT_THRESHOLDS | Default days-before-expiry thresholds (overridden by workspace preferences) | 30,14,7,1,0 | Alerts |
ALERT_MAX_ATTEMPTS | Max delivery retries per alert | 20 | Alerts |
ALERT_RETRY_DELAY_MS | Retry delay in ms | 300000 | Alerts |
ALERT_TEST_UTC_DAY | Test-only scheduler day override | unset | Alerts testing |
GLOBAL_RATE_LIMIT_WINDOW_MS | Global limiter window in ms | 60000 | API rate limiting |
GLOBAL_RATE_LIMIT_MAX | Global limiter max requests per window | 300 (prod) / 1000 (dev,test) | API rate limiting |
GLOBAL_SLOWDOWN_WINDOW_MS | Global slowdown window in ms | 900000 | API rate limiting |
GLOBAL_SLOWDOWN_DELAY_AFTER | Requests before delay is applied | 50 | API rate limiting |
GLOBAL_SLOWDOWN_DELAY_MS | Added delay per request after threshold | 500 | API rate limiting |
LOGIN_RATE_LIMIT_WINDOW_MS | Login limiter window in ms | 900000 | Auth rate limiting |
LOGIN_RATE_LIMIT_MAX | Login attempts per window | 10 (prod) / 500 (dev,test) | Auth rate limiting |
LOGIN_EMAIL_RATE_LIMIT_MAX | Login attempts per email per window | 5 (prod) / 500 (dev,test) | Auth rate limiting |
PASSWORD_RESET_RATE_LIMIT_WINDOW_MS | Password reset limiter window in ms | 900000 | Auth rate limiting |
PASSWORD_RESET_RATE_LIMIT_MAX | Password reset attempts per window | 5 (prod) / 100 (dev,test) | Auth rate limiting |
PASSWORD_RESET_EMAIL_RATE_LIMIT_MAX | Password reset attempts per email per window | 3 (prod) / 200 (dev,test) | Auth rate limiting |
EMAIL_VERIFICATION_RATE_LIMIT_WINDOW_MS | Email verification limiter window in ms | 3600000 | Auth rate limiting |
EMAIL_VERIFICATION_RATE_LIMIT_MAX | Email verification attempts per window | 20 (prod) / 500 (dev,test) | Auth rate limiting |
AUTH_SLOWDOWN_WINDOW_MS | Auth slowdown window in ms | 900000 | Auth rate limiting |
AUTH_SLOWDOWN_DELAY_AFTER | Auth requests before slowdown | 5 (prod) / 1000 (dev,test) | Auth rate limiting |
AUTH_SLOWDOWN_DELAY_MS | Auth slowdown delay per request in ms | 500 | Auth rate limiting |
AUTH_SLOWDOWN_MAX_DELAY_MS | Auth slowdown max delay in ms | 10000 | Auth rate limiting |
API_RATE_LIMIT_WINDOW_MS | Plan-aware API limiter window in ms | 900000 | API rate limiting |
TEST_API_RATE_LIMIT_WINDOW_MS | Test/dev API limiter window in ms | 900000 | API rate limiting |
TEST_API_RATE_LIMIT_MAX | Test/dev API limiter max requests | 1000 (prod) / 10000 (dev,test) | API rate limiting |
DELIVERY_WINDOW_DEFAULT_START | Default delivery window start (UTC, overridden by workspace preferences) | 00:00 | Alerts |
DELIVERY_WINDOW_DEFAULT_END | Default delivery window end (UTC, overridden by workspace preferences) | 23:59 | Alerts |
DELIVERY_WINDOW_DEFAULT_TZ | Default delivery timezone (overridden by workspace preferences) | UTC | Alerts |
DELIVERY_WINDOW_DEFERRAL_MS | Defer delay when outside window | unset | Alerts |
MAX_WEBHOOKS | Max webhooks per workspace | unset | Alerts/webhooks |
TEST_WEBHOOK_RATE_LIMIT_1M_WINDOW_MS | Test webhook short limiter window in ms | 60000 | Alerts/webhooks |
TEST_WEBHOOK_RATE_LIMIT_1M_MAX | Test webhook max requests in short window | 5 | Alerts/webhooks |
TEST_WEBHOOK_RATE_LIMIT_5M_WINDOW_MS | Test webhook long limiter window in ms | 300000 | Alerts/webhooks |
TEST_WEBHOOK_RATE_LIMIT_5M_MAX | Test webhook max requests in long window | 10 | Alerts/webhooks |
TEST_WEBHOOK_COOLDOWN_MS | Per-user test webhook cooldown after each attempt | 5000 | Alerts/webhooks |
WEBHOOK_ALLOW_ALL_HOSTS | Allow all webhook destinations when true | false | Webhook security |
WEBHOOK_PROVIDER_HOSTS | Extra allowed provider hosts | empty (built-in list still allowed) | Webhook security |
WEBHOOK_EXTRA_PROVIDER_HOSTS | Additional allowed hosts | empty | Webhook security |
VAULT_ADDRESS_ALLOWLIST | Vault integration host allowlist | empty (no host restriction) | Integration security |
AZURE_VAULT_ADDRESS_ALLOWLIST | Azure Key Vault host allowlist | empty (no host restriction) | Integration security |
GITHUB_ADDRESS_ALLOWLIST | GitHub host allowlist | empty (no host restriction) | Integration security |
GITLAB_ADDRESS_ALLOWLIST | GitLab host allowlist | empty (no host restriction) | Integration security |
INTEGRATION_SCAN_LIMITS | JSON plan-to-limit map (core defaults unlimited) | {"oss":Infinity} | Integration quotas |
DOMAIN_CHECKER_DISCOVERY_LIMITS | Domain checker discovery result cap map (plan:value) | oss:10000000 | Domain checker |
DOMAIN_CHECKER_IMPORT_LIMITS | Domain checker import request cap map (plan:value) | oss:50000 | Domain checker |
DOMAIN_CHECKER_MAX_RESULTS | Direct override for discovery results, capped internally at 25,000,000 | unset | Domain checker |
DOMAIN_CHECKER_IMPORT_MAX_CERTIFICATES | Direct override for import certificates per request, capped at 200,000 | unset | Domain checker |
CONTACT_GROUP_LIMITS | JSON plan-to-limit map (core defaults unlimited) | {"oss":Infinity} | Contact groups |
CONTACT_GROUP_MEMBER_LIMITS | JSON plan-to-limit map (core defaults unlimited) | {"oss":Infinity} | Contact groups |
WORKSPACE_PLAN_LIMITS | JSON plan-to-limit map (core defaults unlimited) | {"oss":Infinity} | Workspaces |
MEMBER_PLAN_LIMITS | JSON plan-to-limit map (core defaults unlimited) | {"oss":Infinity} | Workspace members |
CertOps (certificate operations)
Enabling and required keys
| Variable | Description | Default value | Scope |
|---|---|---|---|
CERTOPS_ENABLED | Enable the certificate operations layer. When enabled, TokenTimer maintains a managed-certificate inventory linked to cert-category tokens, accepts public certificate (PEM) import, and bridges observations from HTTPS endpoint/domain monitors into the inventory (when the monitor has a linked token). When disabled, CertOps API endpoints return 404. | false | CertOps |
CERTOPS_SIGNING_ENCRYPTION_KEY | 64 hex characters (32 bytes). Wraps the control-plane Ed25519 job-signing private key at rest. Required before any agent can be dispatched signed work: operations that need the key fail closed with CERTOPS_SIGNING_ENCRYPTION_KEY_MISSING when it is unset or malformed. | none | Agents |
CERTOPS_REGISTRATION_ENCRYPTION_KEY | 64 hex characters (32 bytes). Wraps agent registration credentials at rest. Required for agent registration to succeed. | none | Agents |
SESSION_SECRETGenerate each with openssl rand -hex 32, set them before enabling agents, and back them up. Losing CERTOPS_SIGNING_ENCRYPTION_KEY makes the stored signing key unreadable, and you must rotate to a new signing key and have every agent re-pin it. Rotating either value without a controlled migration has the same effect. Both are read from the environment only; they are never stored in the database.
If you only use CertOps for inventory and monitoring (no agents), CERTOPS_ENABLED alone is enough. The two encryption keys are needed for the agent execution plane.
Renewal scheduling
| Variable | Description | Default value | Scope |
|---|---|---|---|
CERTOPS_RENEWAL_THRESHOLD_DAYS | Schedule a renewal when a managed certificate expires within this many days. | 30 | Renewals |
CERTOPS_RENEWAL_PER_CA_CAP | Maximum in-flight renewals per CA endpoint per workspace, so one CA cannot be flooded. Exceeding it defers work rather than failing it (CERTOPS_RENEWAL_PER_CA_CAP_EXCEEDED). | 5 | Renewals |
CERTOPS_JOB_LEASE_SECONDS | How long an agent's claim on a job stays valid before it must be renewed. Raise this if legitimate renewals routinely take longer. | 900 | Jobs |
CERTOPS_LEASE_HARD_GRACE_MS | Extra time a still-heartbeating agent gets before its expired-lease job is judged. | 3600000 | Jobs |
Lease expiry never silently retries work that may already have changed a host. See Reconciling interrupted jobs.
Agent fleet and compatibility
| Variable | Description | Default value | Scope |
|---|---|---|---|
CERTOPS_AGENT_OFFLINE_AFTER_MS | Heartbeat silence after which an agent counts as offline. Observational: it never retires an agent by itself. | 600000 | Agents |
CERTOPS_AGENT_CLOCK_DRIFT_WARN_MS | Reported agent clock offset above which the fleet view warns. | 5000 | Agents |
CERTOPS_AGENT_CLOCK_DRIFT_ALERT_MS | Reported agent clock offset above which the fleet view alerts. Signed jobs enforce a validity window, so a badly skewed host cannot execute work. | 30000 | Agents |
CERTOPS_AGENT_MIN_PROTOCOL_VERSION | Oldest accepted agent protocol version. | 1.0.0 | Agents |
CERTOPS_AGENT_MAX_PROTOCOL_VERSION | Newest accepted agent protocol version. | 1.999.999 | Agents |
CERTOPS_AGENT_MIN_AGENT_VERSION | Oldest accepted agent build. Raise this to force a fleet upgrade. | 0.1.0 | Agents |
CERTOPS_AGENT_MAX_AGENT_VERSION | Newest accepted agent build. | 99.999.999 | Agents |
CERTOPS_REGISTRATION_REPLAY_TTL_MS | How long a registration attempt is remembered for replay rejection. | 900000 | Agents |
Narrowing the version windows blocks incompatible agents at registration and heartbeat with CERTOPS_AGENT_COMPATIBILITY_BLOCKED, which is the supported way to stop an old build from claiming work during a staged upgrade.
Maintenance sweeps
The CertOps maintenance worker runs five sweeps. Each has an enable flag (default true) and a timeout in milliseconds:
| Sweep | Enable variable | Timeout variable | What it does |
|---|---|---|---|
| Lease reaper | CERTOPS_SWEEP_LEASE_REAPER_ENABLED | CERTOPS_SWEEP_LEASE_REAPER_TIMEOUT_MS | Requeues or flags jobs whose lease expired |
| Stale agents | CERTOPS_SWEEP_STALE_AGENTS_ENABLED | CERTOPS_SWEEP_STALE_AGENTS_TIMEOUT_MS | Marks agents offline after heartbeat silence |
| Nonce sweep | CERTOPS_SWEEP_NONCE_ENABLED | CERTOPS_SWEEP_NONCE_TIMEOUT_MS | Deletes consumed/expired dispatch nonces past their grace window |
| Registration replay | CERTOPS_SWEEP_REGISTRATION_REPLAY_ENABLED | CERTOPS_SWEEP_REGISTRATION_REPLAY_TIMEOUT_MS | Expires registration replay records |
| Renewal scheduler | CERTOPS_SWEEP_RENEWAL_SCHEDULER_ENABLED | CERTOPS_SWEEP_RENEWAL_SCHEDULER_TIMEOUT_MS | Creates renewal jobs for certificates near expiry |
Disabling the renewal-scheduler sweep stops new automatic renewal jobs from being created while leaving existing jobs, dispatch, and reporting untouched. Disabling the lease reaper leaves interrupted jobs in place indefinitely, so keep it on outside of debugging.
cert-manager controller
These apply to the Kubernetes controller, not the API or worker:
| Variable | Description | Default value | Scope |
|---|---|---|---|
CERTOPS_CONTROLLER_MODE | observe (report only) or provision (create authorized Certificate resources). | observe | Controller |
CERTOPS_WATCH_NAMESPACES | Comma-separated namespaces to watch. | all watched namespaces | Controller |
CERTOPS_CLUSTER_WIDE | Watch cluster-wide instead of a namespace list. | false | Controller |
CERTOPS_RECONCILE_INTERVAL | Reconcile loop interval. | see chart defaults | Controller |
CERTOPS_HEALTH_PORT | Port for the controller's health endpoint. | see chart defaults | Controller |
CERTOPS_SHUTDOWN_TIMEOUT | Graceful shutdown budget. | see chart defaults | Controller |
The controller is disabled by default in the Helm chart and defaults to observe mode. See CertOps with ACME and cert-manager.
Operator commands
Some CertOps operations are deployment-level rather than per-workspace, and are exposed as commands rather than API routes:
# Inspect / rotate the control-plane job-signing key
pnpm certops:rotate-signing-key status
pnpm certops:rotate-signing-key begin
pnpm certops:rotate-signing-key complete
See Rotating the job-signing key.
TokenTimer stores only public certificate material (fingerprints, serials, issuers, subjects, SANs, validity, chains) and external key references. Requests containing private key material are rejected with HTTP 422.
For the flag resolution order and rollout guidance, see Enable CertOps.
Worker scheduling
TokenTimer runs five background workers:
- Alert discovery scans tokens and queues expiry alerts.
- Alert delivery processes the alert queue and sends notifications (email, webhooks, WhatsApp).
- Auto-sync runs scheduled integration scans (for example Vault or GitHub imports).
- Endpoint check monitors HTTPS endpoints and their SSL certificates.
- Weekly digest sends the weekly token-expiry summary.
How they are scheduled depends on the deployment method:
- Docker Compose: each worker runs as its own long-running container (
worker-discovery,worker-delivery,worker-auto-sync,worker-endpoint-check,worker-weekly-digest) using the built-in runner. The runner schedules jobs in-process from a cron expression; the containerrestart: unless-stoppedpolicy is crash recovery only, not the scheduler. - Helm (Kubernetes): each worker is a Kubernetes CronJob that starts a one-shot process per scheduled run. Schedules come from
worker.cronjobs.<name>.scheduleinvalues.yaml, not from theWORKER_*_CRONvariables below. The chart defaults match the runner defaults.
A cron expression is a five-field schedule string (minute hour day-of-month month day-of-week), for example */5 * * * * for every 5 minutes.
| Variable | Description | Default value | Scope |
|---|---|---|---|
WORKER_DISCOVERY_CRON | Alert discovery schedule (scan tokens, queue expiry alerts) | */5 * * * * | Worker (Compose) |
WORKER_DELIVERY_CRON | Alert delivery schedule (send queued notifications), offset 1 min after discovery | 1/5 * * * * | Worker (Compose) |
WORKER_AUTO_SYNC_CRON | Auto-sync schedule (scheduled integration scans) | */1 * * * * | Worker (Compose) |
WORKER_ENDPOINT_CHECK_CRON | Endpoint/SSL check schedule | */1 * * * * | Worker (Compose) |
WORKER_WEEKLY_DIGEST_CRON | Weekly digest schedule (Monday 09:00) | 0 9 * * 1 | Worker (Compose) |
Cron expressions are evaluated in the container timezone. Compose worker services set TZ: ${TZ:-UTC}, so set TZ in deploy/compose/.env (for example TZ=Europe/Zurich) to shift schedules to local time.
Run on start
In cron mode, workers wait for their next scheduled run and do not run immediately at process start. Set a run-on-start variable to true to also run the job once at startup.
| Variable | Description | Default value | Scope |
|---|---|---|---|
WORKER_RUN_ON_START | Global run-on-start for discovery, delivery, auto-sync, and endpoint check. Not inherited by weekly digest. | false | Worker |
WORKER_DISCOVERY_RUN_ON_START | Per-worker override for alert discovery | false | Worker |
WORKER_DELIVERY_RUN_ON_START | Per-worker override for alert delivery | false | Worker |
WORKER_AUTO_SYNC_RUN_ON_START | Per-worker override for auto-sync | false (code), true (compose) | Worker |
WORKER_ENDPOINT_CHECK_RUN_ON_START | Per-worker override for endpoint check | false | Worker |
WORKER_WEEKLY_DIGEST_RUN_ON_START | Run the weekly digest immediately at startup. Required for weekly digest; it ignores the global variable. | false | Worker |
The Compose worker-auto-sync service sets WORKER_AUTO_SYNC_RUN_ON_START=true by default, so an integration scan runs when that container starts.
In interval mode, discovery, delivery, auto-sync, and endpoint check run once at startup by default (the weekly digest still does not).
Supported cron syntax
The runner accepts standard five-field cron expressions with:
*for any value- step values:
*/nandn/m(start atn, repeat everym) - exact integers, ranges (
n-m), and comma-separated lists - day-of-week
0-7, where both0and7mean Sunday
Not supported: named months or weekdays (JAN, MON), and the L, W, and # modifiers. Invalid expressions fail at worker startup with an error naming the variable.
Interval mode
Cron mode is the default. To use fixed millisecond intervals instead, set the worker's cron variable to interval (or 0, false, off) and set the matching interval variable:
WORKER_DISCOVERY_CRON=interval
WORKER_DISCOVERY_INTERVAL_MS=60000
WORKER_*_INTERVAL_MS variables are ignored while the worker is in cron mode.
| Variable | Description | Default value | Scope |
|---|---|---|---|
WORKER_DISCOVERY_INTERVAL_MS | Discovery interval (ms) | 60000 | Worker |
WORKER_DELIVERY_INTERVAL_MS | Delivery interval (ms) | 30000 | Worker |
WORKER_AUTO_SYNC_INTERVAL_MS | Auto-sync interval (ms) | 300000 | Worker |
WORKER_ENDPOINT_CHECK_INTERVAL_MS | Endpoint check interval (ms) | 60000 | Worker |
WORKER_WEEKLY_DIGEST_INTERVAL_MS | Weekly digest interval (ms) | 86400000 | Worker |
Failure behavior
| Variable | Description | Default value | Scope |
|---|---|---|---|
WORKER_EXIT_ON_ERROR | When false, job failures are logged (worker-runner-job-failure) and the runner keeps running. When true, the process exits on the first unhandled job error and the container restart policy recovers it. | false | Worker |
For worker health metrics (runner_up, delivery and digest gauges), see Monitoring and metrics.
Metrics and observability
| Variable | Description | Default value | Scope |
|---|---|---|---|
ENABLE_METRICS | Enable /metrics and metric push attempts when true | false | API, worker |
PUSHGATEWAY_URL | Prometheus Pushgateway endpoint | unset | Worker metrics |
ENVIRONMENT_SUFFIX | Metrics environment label (if you have multiple instances) | unset | Worker metrics |
WhatsApp (Twilio)
| Variable | Description | Default value | Scope |
|---|---|---|---|
TWILIO_ACCOUNT_SID | Twilio account SID | unset | |
TWILIO_AUTH_TOKEN | Twilio auth token | unset | |
TWILIO_WHATSAPP_FROM | WhatsApp sender (E.164) | unset | |
TWILIO_WHATSAPP_ALERT_CONTENT_SID_EXPIRES | Content template SID for expiring alerts | unset | |
TWILIO_WHATSAPP_ALERT_CONTENT_SID_EXPIRED | Content template SID for expired alerts | unset | |
TWILIO_WHATSAPP_ALERT_CONTENT_SID_ENDPOINT_DOWN | Content template SID for endpoint down alerts | unset | |
TWILIO_WHATSAPP_ALERT_CONTENT_SID_ENDPOINT_RECOVERED | Content template SID for endpoint recovered alerts | unset | |
TWILIO_WHATSAPP_WEEKLY_DIGEST_CONTENT_SID | Content template SID for weekly digest | unset | |
TWILIO_WHATSAPP_TEST_CONTENT_SID | Content template SID for test messages | unset | |
WHATSAPP_RATE_PER_MIN | Outbound WhatsApp message rate cap | unset | WhatsApp throttling |
WHATSAPP_DRY_RUN | Log messages without sending when true | false | WhatsApp testing |
Admin setup: Twilio WhatsApp
Use this flow when you want to enable WhatsApp notifications, expiry templates, weekly digest, and endpoint alert templates.
1. Configure Twilio credentials in System Settings. As an admin, open System Settings > WhatsApp (Twilio) and fill in the Account SID, Auth Token, and WhatsApp From Number. Optional template SIDs configurable in the UI: Alert (Expires), Alert (Expired), Test Message, and Weekly Digest.
2. Configure endpoint alert template SIDs at deployment level. Endpoint down and endpoint recovered WhatsApp alerts use dedicated deployment variables:
TWILIO_WHATSAPP_ALERT_CONTENT_SID_ENDPOINT_DOWNTWILIO_WHATSAPP_ALERT_CONTENT_SID_ENDPOINT_RECOVERED
Set them in your deployment method: deploy/compose/.env for Compose, deploy/helm/values.yaml for Helm, or your own secret/config injection. After changing these values, restart the delivery worker so it picks up the new template SIDs.
3. Create Twilio content templates. Create two approved Twilio WhatsApp templates for endpoint down and endpoint recovered. The exact template bodies, placeholder contracts, and copy-ready examples for all TokenTimer templates are documented in Contact groups & channels → WhatsApp templates setup.
Variables sent by TokenTimer for endpoint WhatsApp alerts: recipient_name, endpoint_name, endpoint_url, token_name, detected_at.
4. Verify.
- Use Send test WhatsApp in System Settings to verify base Twilio credentials and sender configuration.
- Trigger an endpoint outage and confirm the delivery log shows channel
whatsapp. - Confirm the delivery metadata records
template_kind = endpoint_downfor outage alerts andtemplate_kind = endpoint_recoveredfor recovery alerts.
Legacy and compatibility
| Variable | Description | Default value | Scope |
|---|---|---|---|
PLAN_API_LIMITS | API limiter plan map override (parsed as plan:value, fallback oss:6000) | unset | API middleware |
RATE_LIMIT_WINDOW | Config package generic rate-limit window (ms) | 60000 | Shared config consumers |
RATE_LIMIT_MAX | Config package generic rate-limit max requests | 100 | Shared config consumers |