Configuration - ThinkHumanDotDev/marmot GitHub Wiki

Configuration

Marmot is configured through environment variables. Every variable is declared and validated in src/env.ts (zod) when a process starts, so a typo or a missing required value fails fast with a readable message instead of a half-working install. .env.example at the repository root lists all of them for local development; docker/.env.example is the shorter compose variant.

Three processes read the environment: web (Next.js + Payload: UI, API, status pages), worker (checks, heartbeats, notifications, retention) and realtime (socket.io). With MARMOT_ROLE=all one container runs all three. The Read by column below tells you which process must see a change; in a compose stack the .env file is passed to all three, so you can simply restart the stack.

Boolean variables accept 1, true, yes, on (case-insensitive) as true and anything else as false.

The marmot command-line tool is a client, not one of these processes: its variables (MARMOT_URL, MARMOT_API_KEY, MARMOT_ORG, …) are described in CLI and monitors as code.

Core

Variable Default Read by Description
PAYLOAD_SECRET β€” (required) all Signs session tokens and encrypts secrets at rest. At least 16 characters; use openssl rand -hex 32. Changing it signs everyone out.
NEXT_PUBLIC_SERVER_URL http://localhost:3000 all The URL users open Marmot at. Used for CORS/CSRF, invitation and password-reset emails, the OIDC redirect URI, status-page links, custom-domain detection and the realtime CORS origin. Must match the public https:// URL behind a proxy.
ADDITIONAL_ORIGINS (empty) web Extra CORS/CSRF origins, comma-separated (e.g. a second hostname behind the same proxy).
MARMOT_ROLE all all Which process this container runs: web, worker, realtime, all, or probe (a probe agent). The compose file sets it per service.
NODE_ENV development all development, test or production. The Docker image sets production, which enables HSTS, refuses plain-http:// OIDC issuers and makes the database adapters require migrations instead of pushing the schema.
LOG_LEVEL info all pino level: fatal, error, warn, info, debug, trace. Logs are JSON lines on stdout.

Database

Variable Default Read by Description
DATABASE_ADAPTER postgres all postgres, mongodb or sqlite. SQLite is for local development only (no migrations, pushes its schema).
DATABASE_URL β€” (required) all Connection string for the adapter: postgres://user:pass@host:5432/marmot, mongodb://host:27017/marmot, file:./data/marmot.db. Inside compose the host is the service name (postgres, mongo).

Postgres migrations live in src/migrations/postgres and run when the web role starts (or with the image's migrate command). MongoDB needs no schema migrations; indexes are created on boot.

Redis

Variable Default Read by Description
REDIS_URL redis://localhost:6379 all BullMQ queues and job schedulers (worker, and the web process when monitors change) plus socket.io pub/sub (realtime, and the emitters in web and worker). Run Redis with maxmemory-policy noeviction.

On networks where the Redis hostname resolves only to IPv6 (Railway's *.railway.internal), append ?family=0 to REDIS_URL so the client looks up IPv6 addresses too; it defaults to IPv4.

Redis holds only queue state and live socket rooms. It does not need a backup: the worker re-creates every job scheduler from the database on start.

Realtime

Variable Default Read by Description
REALTIME_PORT 3001 realtime Port the socket.io server listens on. GET /healthz on the same port is the health probe.
NEXT_PUBLIC_REALTIME_URL (empty) web (build time) Origin of the socket server when it is not reachable at /socket.io on the web origin. Leave empty with the bundled Caddy or any proxy that routes /socket.io/* to the realtime process. Inlined into the browser bundle at pnpm build; the published image has it empty.

See Realtime origin below.

Storage

Uploads (organization logos, status-page logos) go to local disk unless S3_BUCKET is set, in which case @payloadcms/storage-s3 is registered and the other S3_* variables configure the client.

Variable Default Read by Description
UPLOADS_DIR uploads web Local upload directory, relative to the working directory. The image uses /app/uploads (a volume).
S3_BUCKET β€” web Bucket name. Setting it switches storage to S3.
S3_REGION β€” web Region (auto for Cloudflare R2 and most S3-compatible stores).
S3_ENDPOINT β€” web Custom endpoint for S3-compatible services (R2, MinIO, …). Leave unset for AWS.
S3_ACCESS_KEY_ID β€” web Access key.
S3_SECRET_ACCESS_KEY β€” web Secret key.
S3_FORCE_PATH_STYLE false web Use path-style URLs (https://endpoint/bucket/key), needed by MinIO and some proxies.

Email

Marmot sends invitations, password-reset mail, email verification and sign-in links and status page subscriber emails (confirmations and announcements, sent by the worker) through the Payload email adapter, over a pooled SMTP connection (at most five at a time per process). Without SMTP_HOST the messages are written to the process log instead of being sent, which is enough to copy an invitation link during evaluation. Subscriber SMS use a Twilio notification channel chosen per page, so they need no variable either. The smtp notification provider can reuse these settings (Use the server SMTP settings); the last two variables decide who may do that and how much mail it may send.

Variable Default Read by Description
SMTP_HOST β€” web, worker SMTP server. Unset = log mail to the console.
SMTP_PORT 587 web, worker SMTP port.
SMTP_USER β€” web, worker Username (optional for unauthenticated relays).
SMTP_PASSWORD β€” web, worker Password.
SMTP_SECURE false web, worker true for implicit TLS (usually port 465); false uses STARTTLS when offered.
EMAIL_FROM Marmot <marmot@localhost> web, worker Sender address, Name <address> form allowed.
NOTIFICATIONS_SERVER_SMTP superadmin web, worker Who may set up an smtp notification channel that sends through the settings above: all (anyone who manages channels), superadmin (instance superadmins only) or off (nobody; such channels fail to deliver).
NOTIFICATIONS_SERVER_SMTP_RATE 60 web, worker Messages per organization per hour sent through the server SMTP settings, test messages included. 0 = unlimited.

Notification mail sent through the server settings is limited to 10 recipients per message (to, cc and bcc combined). In superadmin mode, channels set up before the upgrade keep sending; see Deployment for how to list them. Details: Notifications.

Authentication

Variable Default Read by Description
DISABLE_SIGNUP false web Default for the allowSignup instance setting. When sign-up is off, accounts are created only through the setup wizard, invitations, or SSO logins that match a pending invitation.
REQUIRE_EMAIL_VERIFICATION false web Default for the requireEmailVerification instance setting: new self-service accounts confirm their address by email before they can create organizations, invitations or notification channels. See Organizations and members β†’ Email verification.
MAGIC_LINK_ENABLED false web Default for the magicLinkEnabled instance setting: "Email me a sign-in link" on the login page (single-use, 15 minutes; needs SMTP). Off in SSO-only mode. See Organizations and members β†’ Sign-in links.
OIDC_ISSUER_URL β€” web Issuer of your OpenID Connect provider (its iss value). Discovery is read from <issuer>/.well-known/openid-configuration.
OIDC_CLIENT_ID β€” web Client id registered at the provider.
OIDC_CLIENT_SECRET β€” web Client secret. All three OIDC_ISSUER_URL, OIDC_CLIENT_ID and OIDC_CLIENT_SECRET must be set to enable the SSO button.
OIDC_DISPLAY_NAME Single sign-on web Label of the login button ("Continue with …").
OIDC_AUTO_PROVISION true web Create a Marmot account on first SSO login. false only lets existing users (matched by subject or verified email) in.
OIDC_SCOPES openid email profile web Scopes requested from the provider.
OIDC_GROUP_CLAIM groups web Claim listing the user's groups; dotted paths read nested claims (realm_access.roles). See Single sign-on β†’ Groups.
OIDC_ALLOWED_GROUPS β€” web Comma-separated, case-insensitive allow-list. When set, users in none of these groups cannot sign in or be provisioned through the OIDC client.
OIDC_ROLE_MAPPING β€” web JSON object from group to {"org": "<slug>", "role": "owner|admin|member|viewer"} (or a list of them, or {"role": "superadmin"}), applied on every login. Invalid JSON stops startup.
OIDC_ROLE_MAPPING_REMOVE false web Also remove memberships of mapped organizations (and superadmin) the groups no longer grant. The last owner / superadmin is never removed.
OIDC_DISABLE_LOCAL_LOGIN false web SSO-only mode: no password logins, sign-ups or password resets. See Single sign-on β†’ SSO-only mode.
OIDC_BREAK_GLASS false web With OIDC_DISABLE_LOCAL_LOGIN, superadmins can still sign in with their password from /login?local=1 (audited as auth.break_glass).
GITHUB_CLIENT_ID β€” web GitHub OAuth app client id. With GITHUB_CLIENT_SECRET, enables "Continue with GitHub"; callback URL <NEXT_PUBLIC_SERVER_URL>/api/auth/sso/github/callback.
GITHUB_CLIENT_SECRET β€” web GitHub OAuth app client secret.
GOOGLE_CLIENT_ID β€” web Google OAuth client id. With GOOGLE_CLIENT_SECRET, enables "Continue with Google"; redirect URI <NEXT_PUBLIC_SERVER_URL>/api/auth/sso/google/callback.
GOOGLE_CLIENT_SECRET β€” web Google OAuth client secret.
API_KEY_RATE_LIMIT 600 web Management API requests per minute per API key; 0 turns the limit off.
API_KEY_WRITE_RATE_LIMIT 60 web Of those, writes (POST/PUT/PATCH/DELETE) per minute per key; 0 turns the limit off.
METRIC_PUSH_RATE_LIMIT 60 web Custom metric pushes per minute per API key (or user) and metric, on top of API_KEY_WRITE_RATE_LIMIT; 0 turns the limit off.
STATUS_PAGE_SESSION_DAYS 30 web Lifetime (days, 1–365) of the cookie a visitor gets after signing in to a protected status page (password or email link).

Full setup guide with provider walkthroughs: Single sign-on.

Monitoring

Variable Default Read by Description
KEEP_DATA_PERIOD_DAYS 365 web, worker Default for the keepDataPeriodDays instance setting: how long daily aggregates and important heartbeats are kept. Raw heartbeats live 24 h, minutely buckets 24 h, hourly 30 d.
AUDIT_LOG_RETENTION_DAYS 365 worker Days the audit log keeps its rows; the hourly retention job deletes older ones. 0 keeps them forever.
WEBHOOK_DELIVERY_RETENTION_DAYS 14 worker Days the webhook delivery log keeps a delivery (1–90); the hourly retention job deletes older ones.
WEBHOOK_DISABLE_AFTER_FAILURES 5 worker An outbound webhook endpoint is disabled, and the organization's admins are emailed, after this many deliveries in a row failed for good (each after its retries). 0 never disables.
OTLP_EXPORT_ENABLED true worker, web OpenTelemetry export of check metrics to the organizations' collectors. false turns it off for the whole instance.
OTLP_EXPORT_INTERVAL_MS 5000 worker, web How long data points wait to be batched before they are sent (100–300000 ms).
OTLP_EXPORT_MAX_BATCH 1000 worker, web Data points per export request; a full batch is sent at once.
OTLP_EXPORT_MAX_QUEUE 20000 worker, web Data points buffered per collector while it is slow or down; beyond that the oldest are dropped.
OTLP_EXPORT_TIMEOUT_MS 10000 worker, web Timeout of one export request (1000–120000 ms).
WORKER_CONCURRENCY 10 worker Parallel checks (and notification deliveries) per worker process. One worker uses at most one core (about 25 checks/s): keep it at 10–20 and add worker replicas instead (Scaling).
ON_DEMAND_CHECKS_PER_MINUTE 30 web On-demand checks per organization and minute: Check now, the monitor form's Test and their API (POST /api/orgs/:orgId/monitors/:id/check, POST /api/orgs/:orgId/checks). Further requests get 429.
CONNECTIVITY_CHECK_ENABLED false worker Self connectivity check: hold checks of external targets while the worker itself is offline instead of reporting them DOWN.
CONNECTIVITY_CHECK_TARGETS 1.1.1.1:53, 8.8.8.8:53, https://www.google.com/generate_204 worker Probe targets, comma-separated: host:port (TCP connect, [v6]:port for IPv6) or an http(s):// URL (any HTTP response counts). Use your own when these are blocked.
CONNECTIVITY_CHECK_MODE any worker any: online when at least one target answers; all: every target must answer.
CONNECTIVITY_CHECK_INTERVAL 30 worker Seconds between probes (5–3600); the verdict is cached for this long.
CONNECTIVITY_CHECK_TIMEOUT 5 worker Seconds each target gets to answer (1–60).
CONNECTIVITY_CHECK_NOTIFY_EMAIL true worker Email the "checker offline" / "back online" notices to every instance superadmin (needs SMTP_HOST).
CONNECTIVITY_CHECK_NOTIFICATION_ID β€” worker Id of a notification channel that also receives the notices (any provider, e.g. a Telegram bot on a mobile network).
DOCKER_SOCKET_ENABLED true web, worker Allow socket Docker hosts, which talk to the Docker daemon of the worker's own host. Set false on shared installs where users must not reach the local daemon.
MARMOT_DISABLE_ENGINE_HOOKS false web Skip the BullMQ scheduler sync in the monitors collection hooks. Only for tests that run without Redis; the Vitest setup sets it.
MONITOR_DENY_PRIVATE_ADDRESSES false web, worker Refuse monitor checks and notification deliveries to private, loopback, link-local, CGNAT, multicast and container-network addresses (see below). Turn on when people you do not trust can create monitors, e.g. with open sign-up.
MONITOR_DENY_CIDRS β€” web, worker Comma-separated CIDRs that are always refused, guard on or off (e.g. 203.0.113.0/24, 2001:db8::/32). Wins over MONITOR_ALLOW_CIDRS.
MONITOR_ALLOW_CIDRS β€” web, worker Comma-separated CIDRs exempt from the private-address deny list, for internal subnets you do want to monitor (e.g. 10.20.0.0/16).

Certificate transparency alerts

Monitors with Certificate transparency alerts turned on (ctAlerts, see Notifications) are looked up in the certificate transparency logs by the worker's ct-alerts job.

Variable Default Read by Description
CT_SOURCE_URL https://crt.sh/?q={query}&output=json&exclude=expired&deduplicate=Y worker CT source, a URL template. {query} is the crt.sh identity (example.com, or %.example.com for the subdomains, looked up separately), {domain} the hostname, {subdomains} true or false. The answer may be crt.sh JSON or Cert Spotter issuances (expand=dns_names&expand=issuer).
CT_CHECK_INTERVAL_MINUTES 180 worker Minutes between two lookups of a monitor (60–360). After a failed lookup the monitor and the source back off exponentially, up to 24 h.
CT_REQUESTS_PER_MINUTE 6 worker Requests per minute sent to the source (1–120). A 429 or 503 answer pauses every lookup for its Retry-After (at least the backoff).

Ping-by-email

Push monitors can also be pinged by email. Off unless INBOUND_EMAIL_DOMAIN and at least one of the two transports below are set.

Variable Default Read by Description
INBOUND_EMAIL_DOMAIN β€” web, worker Domain of the monitor addresses, <push token>@<domain> (e.g. ping.status.example.com). Point its MX record at the worker, or route it through a mail provider to the webhook.
INBOUND_SMTP_PORT 0 worker Port of the worker's inbound SMTP listener (plain SMTP, no AUTH, no relaying; only addresses of active push monitors are accepted). 0 turns the listener off. Use 25 when the worker is the domain's MX, or a high port behind a relay or port mapping.
INBOUND_SMTP_HOST 0.0.0.0 worker Address the listener binds to.
INBOUND_EMAIL_WEBHOOK_SECRET β€” web Turns on the inbound-parse webhook POST /api/inbound-email (Postmark, Mailgun, SendGrid, Cloudflare Email Workers) and is the secret it requires (at least 16 characters): ?secret=, the Basic auth password or an X-Marmot-Secret header.
INBOUND_EMAIL_MAX_BYTES 1048576 web, worker Largest accepted message in bytes (1 KiB–50 MiB). Larger mail is refused (552 over SMTP, 413 on the webhook).

Inbound alert integrations

Integrations receive alerts from Alertmanager, Grafana or any JSON sender at POST /api/inbound/<token> (and by email at <token>@INBOUND_EMAIL_DOMAIN when ping-by-email is on).

Variable Default Read by Description
INBOUND_ALERT_RATE_LIMIT 120 web Requests per minute per integration token, and per client address when trustProxy is on. Over the limit the endpoint answers 429. 0 turns the limit off.
INBOUND_ALERT_MAX_BYTES 262144 web Largest accepted request body in bytes (1 KiB–10 MiB); larger bodies get 413.

Self connectivity check

When the worker's own uplink or DNS fails, every external monitor would go DOWN at once and every channel would be flooded, although only Marmot's connectivity is down. With CONNECTIVITY_CHECK_ENABLED=true the worker probes CONNECTIVITY_CHECK_TARGETS every CONNECTIVITY_CHECK_INTERVAL seconds (and again whenever a check fails and the last probe is older than 10 s). While the probes fail (per CONNECTIVITY_CHECK_MODE):

  • checks of external targets are skipped and recorded as PENDING heartbeats with the message checker offline. They send no notifications, are left out of the uptime statistics, and leave the monitor's status as it was, so the first check after the outage is judged against the status from before;
  • monitors whose targets are all private or local keep running: IP addresses in the private ranges listed under Private-address guard, localhost, single-label names, .local, .lan, .internal, .home.arpa and .localdomain names, names that resolve only to private addresses, database sockets, Docker socket hosts and groups. Push monitors are held (pushes cannot arrive either);
  • one "checker offline" notice goes to the instance superadmins by email and/or to the channel CONNECTIVITY_CHECK_NOTIFICATION_ID, and one "back online" notice when the probes succeed again (notices that cannot be delivered while offline are retried after every probe). With several worker replicas only one of them sends the notices of an outage;
  • the Marmot UI shows a "Checker offline" banner, GET /api/health reports checker.status = offline and /api/metrics exposes marmot_checker_online 0.

When connectivity returns, the held monitors are checked again at once. The check is off by default.

Private-address guard

With MONITOR_DENY_PRIVATE_ADDRESSES=true the worker checks every address a monitor or notification channel would connect to, after DNS resolution and at connect time (every redirect hop included), and connects only to the address it checked. A refused check is a DOWN heartbeat such as Blocked: db.internal resolves to a private address (MONITOR_DENY_PRIVATE_ADDRESSES).

  • Denied ranges: 0.0.0.0/8, 10.0.0.0/8, 100.64.0.0/10, 127.0.0.0/8, 169.254.0.0/16, 172.16.0.0/12, 192.0.0.0/24, 192.168.0.0/16, 198.18.0.0/15, 224.0.0.0/4 and above, ::, ::1, fc00::/7, fe80::/10, fec0::/10, ff00::/8, 64:ff9b:1::/48, plus IPv6 forms that embed one of the IPv4 ranges (::ffff:127.0.0.1, ::127.0.0.1, NAT64 64:ff9b::/96, 6to4 2002::/16).
  • Precedence: MONITOR_DENY_CIDRS β€Ί MONITOR_ALLOW_CIDRS β€Ί the ranges above.
  • Refused types: tailscale-ping, real-browser and Docker hosts of type socket cannot be saved or run, and Apprise channels cannot send: they reach the network without passing the guard.
  • Saving a monitor, proxy or Docker host whose target is a denied literal (127.0.0.1, 2130706433, [::1], localhost) is refused right away; names are judged when the check runs.
  • Instance-wide settings (SMTP_HOST, DATABASE_URL, REDIS_URL) are not affected.

Malformed CIDRs stop the process at startup with a readable error.

Probe locations

Probe locations run checks from other networks. The first two variables configure the server; the last two are all a probe agent (MARMOT_ROLE=probe) needs. An agent reads no database, Redis or PAYLOAD_SECRET; besides these it honours WORKER_CONCURRENCY, LOG_LEVEL, DOCKER_SOCKET_ENABLED, the MONITOR_* address guard and the CONNECTIVITY_CHECK_* variables (its own uplink check).

Variable Default Read by Description
PROBE_OFFLINE_AFTER 180 web, worker Seconds without contact after which a location is marked offline and its admins are emailed (30–86400). Agents refresh their configuration every third of it, at most every 60 s.
PROBE_RATE_LIMIT 600 web Requests per minute per location token to /api/probe/v1/*; 0 turns the limit off.
MARMOT_URL β€” probe Agent only, required: the Marmot server, e.g. https://marmot.example.com. The agent only makes outbound HTTPS requests to it.
MARMOT_PROBE_TOKEN β€” probe Agent only, required: the location's token (mp_…), shown once when the location is created or its token is rotated.

Hosted instance

Switches for running Marmot as a hosted service. Self-hosted installs leave them off.

Variable Default Read by Description
LANDING_PAGE_ENABLED false web Show a marketing landing page at / to signed-out visitors instead of redirecting to /login. Signed-in users and fresh installs (setup wizard) are unaffected.
TERMS_URL β€” web Your terms of service. When set, linked from the landing and pricing footers, the sign-up form ("By signing up you agree to…") and the Billing tab. Marmot does not host the text.
PRIVACY_URL β€” web Your privacy policy; linked in the same places as TERMS_URL when set.
SOURCE_URL β€” web Target of the "Source" link in the app, sign-in and landing footers (AGPL-3.0 Β§13). Set it when you run a modified build; unset, it links to this repository at the running version's tag.

With LANDING_PAGE_ENABLED and BILLING_ENABLED both on, /pricing lists the purchasable plans with their limits and their monthly and yearly Stripe prices (limits only without STRIPE_SECRET_KEY); it returns 404 otherwise. See Billing.

Demo mode

A self-resetting public demo; see Demo mode. Every reset deletes all data.

Variable Default Read by Description
DEMO_MODE false all Seed the demo dataset, reset it on schedule, simulate checks, send nothing and switch off the features listed in the guide.
DEMO_RESET_INTERVAL_MINUTES 60 web, worker Minutes between two resets (15–10080).
DEMO_MODE_FORCE false all Start although the database holds non-demo accounts, and wipe it on the next reset.

Billing

A scaffold for hosted offerings. Self-hosted installs leave it off; every limit is then unlimited and no Stripe code is loaded. Details in Billing.

Variable Default Read by Description
BILLING_ENABLED false web Enforce plan limits and show the Billing settings tab.
STRIPE_SECRET_KEY β€” web Registers the Stripe plugin and enables Checkout / Billing Portal.
STRIPE_WEBHOOK_SECRET β€” web Signing secret of the Stripe webhook endpoint.
STRIPE_PUBLISHABLE_KEY β€” web Reserved for a future Stripe Elements checkout; not needed today.

Telemetry

Marmot sends nothing by default: no analytics SDK is loaded, no cookie banner appears and nothing calls home. Setting a PostHog key enables opt-in product analytics behind a consent banner; what is collected is documented in Telemetry.

Variable Default Read by Description
NEXT_PUBLIC_POSTHOG_KEY β€” web (build time), worker PostHog project key. Unset = telemetry off.
NEXT_PUBLIC_POSTHOG_HOST https://us.i.posthog.com web (build time), worker PostHog ingestion host (https://eu.i.posthog.com for the EU cloud). Also the target of the /ph rewrite.

Container and compose variables

These are read by docker/entrypoint.sh, docker/docker-compose.yml or the Caddyfile rather than by src/env.ts, so they only matter for Docker deployments.

Variable Default Used by Description
DOMAIN β€” compose β†’ Caddy Hostname for automatic HTTPS. Unset = Caddy serves plain HTTP on port 80 (for use behind your own proxy).
ACME_EMAIL β€” compose β†’ Caddy Contact address passed to the certificate authority.
SITE_ADDRESS :80 Caddyfile Derived from DOMAIN by compose; set it directly when running Caddy by hand.
WEB_UPSTREAM web:3000 Caddyfile host:port of the web process. Override when the proxy cannot reach it as web (the Railway template uses web.railway.internal:3000).
REALTIME_UPSTREAM realtime:3001 Caddyfile host:port of the realtime process, for /socket.io/*.
POSTGRES_PASSWORD marmot compose Password of the bundled Postgres; the default DATABASE_URL picks it up.
MARMOT_VERSION release compose Image tag of ghcr.io/thinkhumandotdev/marmot to run; a release's compose file defaults to that release.
PORT 3000 entrypoint (web) Port of the Next.js server inside the container.
BIND_HOST 0.0.0.0 entrypoint (web) Address the Next.js server listens on. :: listens on IPv6 and IPv4, for IPv6-only private networks such as Railway's.
SKIP_MIGRATIONS false entrypoint (web) true skips migrate on start, for when you run migrations yourself (init container, CI/CD step).
WORKER_SCHEMA_WAIT_MS 120000 worker How long the worker waits for the database schema (migrations running in web) before giving up on boot.

Instance settings

Some options can be changed at runtime by a superadmin without redeploying: in the Marmot UI under Settings β†’ Instance (/{orgSlug}/settings/instance, visible to superadmins only, saved through POST /api/globals/instance-settings) or in the Payload admin panel (System β†’ Instance settings, the instance-settings global). Environment variables provide the defaults; once a value has been saved in the global it takes precedence over the variable. The Instance tab also shows whether SMTP is configured and offers Send test email (POST /api/instance/smtp-test { to? }, superadmin-only; answers 400 while SMTP_HOST is unset).

Setting Default Description
primaryBaseUrl NEXT_PUBLIC_SERVER_URL Public URL used in notifications and status page links.
allowSignup !DISABLE_SIGNUP Whether anyone may create an account (POST /api/users, /signup).
requireEmailVerification REQUIRE_EMAIL_VERIFICATION New self-service sign-ups confirm their address before creating organizations, invitations or notification channels. Existing accounts stay verified.
magicLinkEnabled MAGIC_LINK_ENABLED Passwordless sign-in with a single-use link sent by email (two-factor authentication still applies). Ignored while OIDC_DISABLE_LOCAL_LOGIN is on.
entryPage dashboard dashboard or status-page: what the root URL shows.
tlsExpiryNotifyDays 7, 14, 21 Days before a TLS certificate expires at which to notify.
domainExpiryNotifyDays 7, 14, 21 Days before a domain registration expires at which to notify.
keepDataPeriodDays KEEP_DATA_PERIOD_DAYS Retention of daily aggregates and important heartbeats (0 disables). Honoured by the hourly retention job.
trustProxy false Trust X-Forwarded-* headers from the reverse proxy for client IPs (rate limits, audit log, status page IP allow-lists).
steamApiKey, globalpingApiToken β€” Third-party API keys for the corresponding monitor types (superadmin-only read).

Server code reads the resolved values through getInstanceSettings(payload) (src/server/settings.ts), which caches them for 60 seconds per process; saving the global clears the cache of the web process immediately, the worker and realtime processes pick changes up within a minute.

First-run setup

A fresh install has no users. While that is the case GET /api/setup/status returns { "needsSetup": true }, / and the auth pages redirect to /setup, and the wizard creates the first superadmin plus their organization in one transaction (POST /api/setup) and signs them in. Once any user exists the wizard is closed for good (/setup redirects to /login, the API answers 409); further users join through /signup (if allowSignup) or invitations.

Realtime origin

The browser opens one socket.io connection with credentials, so the realtime server must be reachable on a URL that receives the payload-token cookie. By default the client connects to the page's own origin at /socket.io, and Caddy (or your reverse proxy, see Deployment) forwards that path to the realtime process on REALTIME_PORT. Set NEXT_PUBLIC_REALTIME_URL (e.g. https://realtime.example.com) only when the realtime server is exposed on another origin; it must share the cookie's site (a subdomain of the web app is fine) and allows NEXT_PUBLIC_SERVER_URL in CORS. Being a NEXT_PUBLIC_* variable it is inlined at pnpm build time, so it requires building your own image; the published image expects the same-origin layout.

⚠️ **GitHub.com Fallback** ⚠️