Configuration

Every setting is an environment variable. Most have a sensible default; an install needs only the database, SECRET_KEY and BOOTSTRAP_ADMIN_EMAIL to start. Several (email, the push relay, Slack, incident levels) can also be changed later on the Settings page, which overrides the environment.

Settings marked 🔒 are secrets: put them in a Kubernetes Secret or your .env, never in a values file or a repository.

General

Variable Default What it does
PORT 8080 The private listener: web UI, sign-in, alert ingest, MCP, API, /metrics. Keep it off the internet.

Exposure

Variable Default What it does
PUBLIC_PORT 0 A SECOND listener for the internet. Only what phones and calendar apps need answers there: the API with DEVICE tokens (shd_…) only, phone pairing and calendar feeds. The web UI, sign-in, ingest, MCP, /metrics and /docs answer only on PORT, which stays on the private network. 0 = no public listener (everything on PORT, as before).
PUBLIC_API_URL — The URL phones and calendar apps reach the PUBLIC listener at (pairing links, feed URLs). Unset = PUBLIC_URL.
TRUSTED_PROXIES — Peers allowed to tell us the client address (X-Forwarded-For, or CLIENT_IP_HEADER). Only a request whose socket peer is in one of these is believed; the chain is then read right to left, skipping trusted hops. Default: loopback + private ranges (a proxy in front).
CLIENT_IP_HEADER — Optional single-value header a trusted proxy sets to the client address, e.g. CF-Connecting-IP or X-Real-IP. Read only from a trusted peer.
DEVICE_REQUESTS_PER_MINUTE 600 Rate limits on the public surface (counted in the database: one budget across replicas).
PUBLIC_AUTH_FAILURES_PER_10MIN 300 Failed credentials per client address on the public listener (generous: phones share carrier NAT).
FEED_FETCHES_PER_HOUR 120 Fetches per calendar feed per hour.
FEED_MISSES_PER_10MIN 60 Unknown calendar-feed tokens per client address.
LOGIN_FAILURES_PER_IP 20 Break-glass password sign-in: failures per client address and per email, per 15 minutes.
LOGIN_FAILURES_PER_EMAIL 10 Failed password sign-ins per email address per 15 minutes.

Serving

Variable Default What it does
THREADS 8 Request threads per process. Scale with replicas, never with processes.
CONNECTION_LIMIT 200 Open connections per process.
CHANNEL_TIMEOUT 120 Seconds an idle connection is kept.
SHUTDOWN_DELAY_SECONDS 5 On SIGTERM, keep serving this long so load balancers stop sending first.
SHUTDOWN_GRACE_SECONDS 20 Then finish in-flight requests for up to this long.

Database

Variable Default What it does
POSTGRES_HOST — The Postgres server. Set it for anything real; unset = a local SQLite file (DATABASE_URL), for trying Shorthop out. More than one replica needs Postgres.
POSTGRES_PORT 5432 Postgres port.
POSTGRES_DB shorthop Postgres database.
POSTGRES_USER shorthop Postgres user.
POSTGRES_PASSWORD 🔒 — The Postgres password. POSTGRESQL_PASSWORD is accepted too; POSTGRES_PASSWORD wins.
POSTGRES_SSLMODE disable libpq sslmode: disable, prefer, require, verify-full …
DATABASE_URL sqlite:///shorthop.db Used only when POSTGRES_HOST is unset: a local SQLite file, for trying Shorthop out. Run Postgres for anything real.

Web session

Variable Default What it does
SECRET_KEY 🔒 — Stable in production (a random one per process logs everybody out on every restart).
SESSION_COOKIE_SECURE true Send the session cookie over HTTPS only. Leave true; browsers accept it on http://localhost too.
PUBLIC_URL — The address people open the web UI at, e.g. https://oncall.example.com — used for links in notifications and MCP output.

Google SSO

Variable Default What it does
GOOGLE_CLIENT_ID — Google sign-in (optional). Without it people sign in with a password.
GOOGLE_CLIENT_SECRET 🔒 — Google sign-in client secret.
GOOGLE_REDIRECT_URL — Must be /auth/google/callback, and registered on the Google client.
GOOGLE_ALLOWED_DOMAINS — Only these email domains may sign in with Google (comma-separated). Empty = anyone you have invited.
BOOTSTRAP_ADMIN_EMAIL — First admin on an empty install. Only consulted while the users table is EMPTY, so it can stay set forever without re-creating anyone. Signs in with Google (or a password set later through the API by that admin).

Alert handling

Variable Default What it does
SIGNATURE_IGNORE_LABELS — Labels that identify an INSTANCE of a problem rather than the problem itself. They are dropped when computing an alert's signature, so the same fault on another pod is "the same alert as last time". Overridable per integration.
DEFAULT_ALERT_TIMEOUT_MINUTES 5 A firing alert with no explicit endsAt is resolved this long after it was last seen. Prometheus sends its own endsAt (≈4x resend interval) so this only covers webhooks.
MAX_ALERTS_PER_REQUEST 2000 Hard cap on alerts in one ingest request: marshmallow materialises every row before a handler can count them, so the bound has to be in the schema.
MAX_SILENCE_DAYS 30 Longest silence anyone can set.
MCP_MAX_SILENCE_HOURS 24 Silences created through MCP are capped tighter: an assistant should not be able to mute something for a month.

Background loop

Variable Default What it does
SCHEDULER_INTERVAL_SECONDS 30 How often the leader runs housekeeping (expiry, watchdogs, handovers).
HEARTBEAT_URL — Dead-man's switch: pinged every loop. Point it at something OUTSIDE this cluster (healthchecks.io) so a dead Shorthop is noticed by something other than Shorthop.
HEARTBEAT_DISPATCH_MAX_AGE_SECONDS 60 The heartbeat means "I can DELIVER", not "the process is up": it is withheld while this process's dispatch loop (escalations + sending) has not completed a tick for this long, so a wedged dispatch goes quiet on the outside path too. Several dispatch intervals wide (5 s), so one slow send does not cost a ping.
WATCHDOG_STALE_MINUTES 10 An integration whose Watchdog alert has not been seen for this long is reported as silent.

Shadow comparison against a Prometheus

Variable Default What it does
PROMETHEUS_ALLOWED_HOSTS — An integration may name the Prometheus it receives from; Shorthop then asks it what is firing and compares. The URL is set through the API, so the HOST must be on this list (exact names, comma-separated). Empty = the comparison is off everywhere.
SHADOW_CHECK_INTERVAL_SECONDS 60 How often the shadow comparison asks Prometheus what is firing.

Delivery

Variable Default What it does
DISPATCH_INTERVAL_SECONDS 5 The fast loop: due escalation steps and pending notifications. Separate from the 30 s housekeeping tick because a page that waits 30 s for the loop to come round is a slow page.
DELIVERY_MAX_ATTEMPTS 5 Tries per notification before it is marked failed.
DELIVERY_RETRY_BASE_SECONDS 30 Back-off between tries (doubles each time).
SUPPRESSED_RECHECK_SECONDS 60 A step whose alerts are all silenced or inhibited is held, not skipped, and re-checked this often — so when the silence ends, paging resumes where it was.
SMTP_HOST — Email over SMTP. Unset SMTP_HOST = email delivery off (notifications are logged as failed with a reason, never silently dropped).
SMTP_PORT 587 SMTP port (587 STARTTLS, 465 TLS).
SMTP_USER — SMTP user.
SMTP_PASSWORD 🔒 — SMTP password.
SMTP_FROM — Sender, e.g. Shorthop <alerts@example.com>.
SMTP_STARTTLS true Use STARTTLS.
WEBHOOK_PRIVATE_HOSTS — Webhook channels may not point at private, loopback or link-local addresses (the server would otherwise be a way into its own network). Hosts listed here are exempt — e.g. an internal chat server. Exact host names or IPs. Overridable on the Settings page.
EXPO_ACCESS_TOKEN 🔒 — Optional: Expo's "enhanced push security" access token. Unset works with Expo's defaults.
RELAY_URL — Push relay (app/relay.py). Both set = phones register with the relay and pages go through it (how a self-hosted install reaches the published app). Unset = straight to Expo with this install's own credentials. Overridable on the admin Settings page.
RELAY_KEY 🔒 — Your installation key (shr_…) from https://account.shorthop.io.
RELAY_SYNC_INTERVAL_SECONDS 3600 How often the leader tells the relay which phones checked in, and reads back its warnings.

Slack app

Variable Default What it does
SLACK_BOT_TOKEN 🔒 — Socket Mode: Shorthop opens an OUTBOUND WebSocket to Slack, so buttons in Slack work with no public URL. Both tokens set = on. Overridable on the admin Settings page. SLACK_BOT_TOKEN xoxb-… (OAuth & Permissions → Bot User OAuth Token) SLACK_APP_TOKEN xapp-… (Basic Information → App-Level Tokens, scope connections:write)
SLACK_APP_TOKEN 🔒 — Slack app-level token (xapp-…), for Socket Mode.
SLACK_RECONCILE_SECONDS 30 How often each replica re-reads the Slack settings and (re)connects if they changed.
SLACK_COMMAND /shorthop The slash command the app manifest declares.
INCIDENT_LEVELS — Incident levels (core/levels.py) as JSON; unset = P1–P4 → critical/high/warning/info.
METRICS_TOKEN 🔒 — If set, /metrics needs Authorization: Bearer <token>.

Shorthop Watch

Variable Default What it does
WATCH_FORWARD_MIN_SECONDS 60 A Watchdog is forwarded to its Watch check at most this often. Prometheus re-sends the Watchdog every minute or so; one ping a minute is all a check needs.

Edit this page on GitHub