Skip to content

Configuration reference

Every environment variable Sphericon reads, with its default. You do not need most of them: a working Instance needs only the four required ones and a mail provider (set up in the app, see the Quickstart). The rest switch on specific features.

Not installed yet? Start with Self-hosting.

Configuration is read from environment variables (and, if present, .env files next to the binary). SPHERICON_ENV selects the environment (development by default; set it to production when self-hosting — the published Docker images already default to production).

Variable What it is
DATABASE_URL The PostgreSQL connection string.
SPHERICON_JWT_SECRET Signs session cookies. Generate it with openssl rand -hex 32.
SPHERICON_ENCRYPTION_KEY Encrypts stored provider credentials. Generate it with sphericon genkey.
SPHERICON_APP_URL The public https:// origin of the Instance. Tracking and unsubscribe links are built from it.
Variable Required Default Description
DATABASE_URL yes — PostgreSQL connection string (or DATABASE_URL_FILE; postgres://user:pass@host:5432/db?sslmode=require). Use sslmode=verify-full (with sslrootcert) to also verify the server certificate.
SPHERICON_JWT_SECRET yes in prod — Signing secret for auth tokens (or SPHERICON_JWT_SECRET_FILE). Outside development/test the server refuses to boot if it is empty, shorter than 32 characters, or a known placeholder. Generate one with openssl rand -hex 32.
SPHERICON_JWT_SECRET_SECONDARY no — The second secret of a JWT secret rotation (or SPHERICON_JWT_SECRET_SECONDARY_FILE): accepted when verifying sessions and mailed links, never used to sign. Unset outside a rotation. Same rules as SPHERICON_JWT_SECRET, and different from it. See Rotating the JWT secret.
SPHERICON_SESSION_TTL no 24h Lifetime of a dashboard session (a Go duration, e.g. 12h): the session token expires and the cookie is dropped after it, with no refresh. Must be positive.
SPHERICON_ENCRYPTION_KEY yes — Base64 Tink keyset (or SPHERICON_ENCRYPTION_KEY_FILE) used to encrypt stored provider credentials. Generate one with sphericon genkey (see Generate the keys). Boot fails if missing.
SPHERICON_APP_URL no http://localhost:3000 Public base URL — used when issuing auth tokens and building tracking/unsubscribe links. Set to your real origin. Its host is also the Passkey relying party id (ADR 0052): changing the host invalidates every registered Passkey, so Users sign in with their password (or reset it) and register their Passkeys again. An Instance served on an IP address cannot use Passkeys, because a relying party id must be a domain name.
SPHERICON_TRACKER_URL no (empty: SPHERICON_APP_URL) Public origin the tracker snippet loads /t.js from and posts /collect to, e.g. https://t.example.com; the Tracking settings page builds its snippet and test command on it. The proxy for that host routes to the same Instance. Leave empty on a single host. A verified Tracking domain of a Workspace serves the same tracker for that Workspace’s collect key (ADR 0059).
SPHERICON_MCP_URL no (empty: SPHERICON_APP_URL) Public origin of a dedicated MCP host, e.g. https://mcp.example.com. The MCP resource is then ${SPHERICON_MCP_URL}/mcp while the OAuth issuer and consent screen stay on SPHERICON_APP_URL. Leave empty on a single host.
PORT no 3000 HTTP listen port.
SPHERICON_METRICS_ADDR no — (off) host:port for the opt-in Prometheus listener. Must differ from PORT; see Prometheus metrics.
SPHERICON_INTERNAL_ADDR no — (off) host:port for the opt-in internal listener that serves the edge’s on-demand TLS ask (GET /tls/ask?domain=, always, ADR 0038) and the Service credential endpoints of the Staff surface (these need the staff license). Must differ from PORT and SPHERICON_METRICS_ADDR; see Service credentials.
SPHERICON_TRACKING_CNAME_TARGET no — (off) The edge host a Sending domain’s Tracking domain CNAME (track.<domain>) must point at (ADR 0038), for example edge.example.com. Unset turns the feature off: no tracking record is shown on a Sending domain.
SPHERICON_TRACKING_HOST_HEADER no — (off) Name of the request header your edge sets to the customer’s host (for example X-Forwarded-Host), so a request for a verified Tracking domain is served only link paths and the tracker (/t.js, /collect, for that Workspace’s collect key only; ADR 0038, 0059). Believed only from a peer inside SPHERICON_TRACKING_EDGE_CIDRS; from anyone else it is ignored. Unset: the connection’s Host is used. Have the proxy for the Instance’s own host drop this header.
SPHERICON_TRACKING_EDGE_CIDRS no loopback only (127.0.0.0/8,::1/128) Comma-separated CIDRs the edge connects from: the only peers whose SPHERICON_TRACKING_HOST_HEADER is trusted. A proxy that is not on the same machine must be listed explicitly (for example its Docker network or VPC CIDR); a peer outside the list can not set the host.
SPHERICON_PRESETS_DIR no — (built-in only) Directory of custom Presets: every *.yaml / *.yml file at its root loads beside the built-in ones at start, and a file that does not load or reuses a built-in name stops the start. A Workspace never supplies one. Test a file first with sphericon preset test; see Presets.
SPHERICON_DB_AUTO_MIGRATE no false Apply embedded migrations on startup. Convenient for single-replica; see below.
SPHERICON_DB_MIGRATE_TIMEOUT no 10m With auto-migrate on, the longest a start waits for the migration lock and the run before it fails. 0 is no limit. sphericon migrate itself has no limit.
SPHERICON_CORS_ORIGINS no — Comma/space-separated origins allowed credentialed CORS on the cookie-authenticated API (/site, /auth). Empty means same-origin only, which is what the bundled SPA needs; the bearer-token APIs are unaffected.
SPHERICON_MAX_BODY_BYTES no 1048576 Largest accepted request body (bytes) on every surface except /collect; larger bodies get 413.
SPHERICON_COLLECT_MAX_BODY_BYTES no 512000 Largest accepted /collect batch (POST /collect/events, bytes); larger gets 413.
SPHERICON_COLLECT_MAX_EVENT_BYTES no 32768 Largest single /collect event (bytes): an identify body, or each event inside a batch; larger gets 413.
SPHERICON_RATE_LIMIT_HUMAN_PER_MINUTE no 60 Requests per minute per IP and endpoint on signup, invitation accept and consent confirm; over it 429 with Retry-After; 0 disables.
SPHERICON_RATE_LIMIT_API_BURST_PER_SECOND no 20 Requests per second per Workspace on /api and /mcp (one shared budget); over it 429 with Retry-After; 0 disables
SPHERICON_RATE_LIMIT_API_PER_MINUTE no 600 Requests per minute per Workspace on /api and /mcp, stacked on the burst limit; 0 disables
SPHERICON_RATE_LIMIT_FAILED_AUTH_PER_MINUTE no 30 Failed bearer-token authentications per minute per IP; over it 429; successful ones are not counted; 0 disables
SPHERICON_RATE_LIMIT_TRACKING_PER_MINUTE no 600 Open and click events recorded per minute per IP; over it the redirect or pixel is still served and only the recording is skipped (never a 429); 0 disables.
SPHERICON_RATE_LIMIT_LOGIN_FAILURES no 5 Failed logins per account within 15 minutes before login answers 429 with Retry-After and a doubling delay (1 s up to 15 min, no lockout), even for a correct password; 0 disables.
SPHERICON_RATE_LIMIT_LOGIN_IP_PER_MINUTE no 20 Login requests per minute per client IP; over it 429 with Retry-After; 0 disables.
SPHERICON_RATE_LIMIT_COLLECT_PER_MINUTE no 6000 Requests per minute per Workspace on /collect (its own budget, apart from /api); over it 429 with Retry-After; 0 disables.
SPHERICON_RATE_LIMIT_SERVICE_PER_MINUTE no 600 Requests per minute per Service credential on the internal listener; over it 429 with Retry-After; 0 disables.
SPHERICON_RATE_LIMIT_COLLECT_IP_PER_MINUTE no 300 Requests per minute per client IP on /collect; over it 429 with Retry-After; 0 disables.
SPHERICON_RATE_LIMIT_FORGOT_PASSWORD_PER_ADDRESS_PER_HOUR no 3 Mails sent per address per hour by forgot-password, by registration and by an email-change request (one budget for each, ADR 0031); over it they still answer 202 and send nothing (the answer never reveals whether the address has an account); 0 disables.
SPHERICON_RATE_LIMIT_FORGOT_PASSWORD_IP_PER_HOUR no 10 Forgot-password requests per client IP per hour; over it 429 with Retry-After; 0 disables.
SPHERICON_DB_MAX_OPEN_CONNS no 15 Maximum open connections in the database/sql pool (API, ent, event bus).
SPHERICON_DB_MAX_IDLE_CONNS no SPHERICON_DB_MAX_OPEN_CONNS Maximum idle connections kept in that pool. Must not exceed SPHERICON_DB_MAX_OPEN_CONNS.
SPHERICON_DB_CONN_MAX_LIFETIME no 30m Maximum lifetime of a pooled connection (Go duration).
SPHERICON_PGX_MAX_CONNS no 25 Maximum connections in the river (job queue) pool. river’s own queries (fetch, completion, LISTEN, leader election) run on it; job workers do their database work through the database/sql pool, so it need not match river’s 20 workers (a small pool only queues completions).
SPHERICON_OUTBOX_RETENTION_FLOOR_DAYS no 7 Minimum age (days) before a domain-event outbox row that every consumer has processed is pruned. The prune job runs every 10 minutes.
SPHERICON_UNVERIFIED_USER_RETENTION_DAYS no 7 Age (days) after which a registration whose email was never verified, and that holds no Membership, is deleted by a daily job at 04:00 UTC, freeing its address; 0 disables. A pending Invitation to that address is kept.
SPHERICON_EVENTS_RETENTION_DAYS no 400 Age (days) after which analytical Events are deleted by a daily job at 03:00 UTC; 0 disables. Evidentiary Events (marketing.confirmed, email.complained, email.unsubscribed, permanent email.bounced) are never deleted by age. Segment conditions that look back past this window are capped by it.
SPHERICON_EVENT_LIVE_WINDOW_HOURS no 72 Age (hours) past which the external API refuses a live Event as history: send it with backfill instead. At least 1; a Backfill is never subject to it. /collect is not limited.
SPHERICON_TELEMETRY no unset Decides the anonymous Telemetry report (ADR 0032): true sends it and locks the in-app switch on, false never sends it and locks it off; unset leaves the choice to the Owner (default on).
DO_NOT_TRACK no unset 1 switches the Telemetry report off and locks the in-app switch; it can never switch the report on.
SPHERICON_RELEASE_CHECK no true false switches the daily Release check off (ADR 0032): it asks GitHub for the newest stable release and shows Owners an update banner. It sends nothing about the Instance and is independent of the Telemetry report and DO_NOT_TRACK.
SPHERICON_APP_LOCALE no en Instance-wide UI and email language: en is the only one; anything else becomes en.
SPHERICON_REQUIRE_EMAIL_VERIFICATION no true Whether an account must prove its email address before it can sign in (ADR 0030). Only an explicit false turns it off, for an install without outbound mail: registration then takes a password and yields a verified user with its workspace at once. Any other value keeps it on. Never inferred from the SMTP settings.
SPHERICON_SIGNIN_GOOGLE_CLIENT_ID no none Enables “Continue with Google” on the login and register pages (ADR 0048): the OAuth client id of your Google Cloud app. Set it together with SPHERICON_SIGNIN_GOOGLE_CLIENT_SECRET; one without the other stops the start. Register <SPHERICON_APP_URL>/site/auth/signin/google/callback as the app’s authorized redirect URI. Google’s email is trusted only for a Gmail address or an account of a hosted domain.
SPHERICON_SIGNIN_GOOGLE_CLIENT_SECRET no none The OAuth client secret that goes with SPHERICON_SIGNIN_GOOGLE_CLIENT_ID.
SPHERICON_SIGNIN_GITHUB_CLIENT_ID no none Enables “Continue with GitHub” on the login and register pages (ADR 0048): the OAuth client id of your GitHub OAuth app. Set it together with SPHERICON_SIGNIN_GITHUB_CLIENT_SECRET; one without the other stops the start. Set <SPHERICON_APP_URL>/site/auth/signin/github/callback as the app’s authorization callback URL. GitHub’s primary email is trusted only when GitHub marks it verified.
SPHERICON_SIGNIN_GITHUB_CLIENT_SECRET no none The OAuth client secret that goes with SPHERICON_SIGNIN_GITHUB_CLIENT_ID.
SPHERICON_SIGNUP_OPEN no true Whether anyone who reaches the instance can register (ADR 0042). Only an explicit false closes it: signup then answers 403 with code signup_closed, and a new user comes only through an invitation or sphericon user create-owner. Any other value keeps it open.
SPHERICON_SIGNUP_SURVEY no false Whether a new user answers a four-question sign-up survey once after the first sign-in (ADR 0055). Only an explicit true turns it on; any other value keeps it off. It needs the staff license feature, because only the Staff console reads the answers: without it the survey stays off and the server logs one warning at start. Users who accept an invitation, the first owner made by sphericon user create-owner and users who existed before the switch are not asked. Answers stay in the instance database and are never part of the telemetry report.
SPHERICON_LICENSE_KEY no — Enterprise Edition license key (or SPHERICON_LICENSE_KEY_FILE), a JWT signed with Ed25519 (features claim, standard exp); without it the single binary runs the open-source core only. A key that has expired (valid signature, past exp) does the same instead of failing boot (ADR 0035): the start logs a warning naming the expiry date, the owner’s admin banner shows it and the sphericon_license_expired metric is 1; no Enterprise data is deleted, and setting a valid key and restarting restores the features with it. A malformed key or one whose signature does not verify still fails boot.
SPHERICON_LOG_LEVEL no info Log level.
SPHERICON_LOG_FORMAT no json (text in development) Log format: json or text.
OTEL_SERVICE_NAME no sphericon Service name reported to OpenTelemetry.
OTEL_CONFIG_FILE no unset (no OTLP export) Path to an OpenTelemetry declarative configuration YAML file: you choose which traces, metrics and logs go to which destination.
SPHERICON_SMTP_HOST / SPHERICON_SMTP_PORT / SPHERICON_SMTP_USER / SPHERICON_SMTP_PASS / SPHERICON_SMTP_FROM no SPHERICON_SMTP_PORT=1025 Outbound email over SMTP (SPHERICON_SMTP_PASS_FILE for the password).
SPHERICON_SMTP_ALLOW_PRIVATE_HOSTS no false in production, true otherwise Let a Workspace’s SMTP integration use a loopback or private host and any port. Off in production: tenants may only reach public hosts on ports 25, 465, 587 and 2525. The platform’s own SPHERICON_SMTP_HOST is exempt.
SPHERICON_OUTBOUND_ALLOW no — (nothing allowed) Comma-separated host:port pairs (a name or an IP address, a port each; no wildcards or ranges) that outbound calls (Webhook endpoint deliveries, test events, Automation HTTP steps and the fetch of an MCP client’s metadata document, which any visitor can trigger at /oauth/authorize) may reach although the address is private or loopback, e.g. n8n:5678,node-red:1880, so events can go to a service on your own network. Off by default; redirects are still never followed. A malformed value stops the server at boot. Leave it unset on a multi-tenant host.
SPHERICON_SYSTEM_EMAIL_PROVIDER no smtp Platform (system) email provider: smtp or ses.
SPHERICON_SYSTEM_EMAIL_FROM no noreply@sphericon.localhost From address for platform mail (e.g. welcome emails).
SPHERICON_SES_REGION / SPHERICON_SES_ACCESS_KEY_ID / SPHERICON_SES_SECRET_ACCESS_KEY no — Credentials of an SES-compatible service when using the ses provider.
SPHERICON_COLLECT_SITE_KEY no — Tracker ingestion key.
SPHERICON_BOOTSTRAP_TOKEN no — External-API bootstrap token.
SPHERICON_STAFF_JWT_SECRET yes with the staff license — Signs the platform Staff member session and login challenge (ADR 0026). Required only when the license has the staff feature, then at least 32 characters and different from SPHERICON_JWT_SECRET, or the server refuses to boot. Generate one with openssl rand -hex 32.
SPHERICON_ACCOUNT_LINK_URL no — (off) Address of an external account page. With SPHERICON_ACCOUNT_LINK_SIGNING_KEY, a Workspace Owner sees an Account link that opens it with a signed token; set both or neither. The app never calls this address.
SPHERICON_ACCOUNT_LINK_SIGNING_KEY no — (off) Base64 Ed25519 seed that signs the Account link token (generate with openssl rand -base64 32). The app publishes the matching public key at /.well-known/jwks.json, so the receiver needs no secret.
SPHERICON_STAFF_SESSION_TTL no 4h Lifetime of a Staff member session (a Go duration): shorter than SPHERICON_SESSION_TTL, with no refresh and no “remember me”. Must be positive.
SPHERICON_STAFF_HOLD_MAX_EXPIRY no 720h How far ahead a service may set an Staff hold’s expiry (a Go duration); a later expiry is refused. A hold that is not renewed lapses by itself. Must be positive.
SPHERICON_ABUSE_VOLUME_FLOOR no 1000 Abuse detector (staff license, ADR 0026): the fewest sends in the trailing 24 hours, per Sending domain, for its rates to be judged; below it nothing is warned or frozen. Must be positive.
SPHERICON_ABUSE_WARN_COMPLAINT_PERCENT no 0.1 Complaint rate (percent) at which the abuse detector warns the Workspace owners by email, freezing nothing. Above 0 and below the freeze limit.
SPHERICON_ABUSE_WARN_BOUNCE_PERCENT no 5 Bounce rate (percent) at which the abuse detector warns the owners. Above 0 and below the freeze limit.
SPHERICON_ABUSE_FREEZE_COMPLAINT_PERCENT no 0.3 Complaint rate (percent) at which the abuse detector suspends the whole Workspace (actor system) and tells its owners. At most 100.
SPHERICON_ABUSE_FREEZE_BOUNCE_PERCENT no 10 Bounce rate (percent) at which the abuse detector suspends the whole Workspace. At most 100.
SPHERICON_SUPPORT_EMAIL no — (none) Support contact shown to a Workspace owner with a suspension (banner and notice email) so they can appeal by email. Empty shows none.

Docker and Kubernetes mount secrets as files. DATABASE_URL, SPHERICON_JWT_SECRET, SPHERICON_JWT_SECRET_SECONDARY, SPHERICON_ENCRYPTION_KEY, SPHERICON_SMTP_PASS and SPHERICON_LICENSE_KEY each accept a _FILE sibling (SPHERICON_JWT_SECRET_FILE=/run/secrets/jwt) whose value is a path read at start. The trailing newline is trimmed. A value set directly wins over the file, which is then never read; an unreadable or empty file fails boot with an error naming the _FILE variable.