Which tool answers which question
The heartbeat tells you the server is alive. It does not tell you which app is down, so keep your app-level alerts next to it.
Off-host heartbeat
The control plane checks three things before each ping:- the database answers within a short probe timeout,
- the reconcile loop completed a pass within
APP_HEARTBEAT_RECONCILE_WINDOWSresync intervals (default3), - the local node is registered. A fresh install with no local node yet skips this check.
reason word (store, reconcile or agent) so the monitor can name the cause. When one fails and no failure URL is set, nothing is sent, and the monitor alerts after its grace period.
The default ping is a POST with a small JSON body:
"status":"degraded" and adds the reason field. The body holds no hostnames, app names or identifiers. Set APP_HEARTBEAT_BODY=off to send no body at all.
Set it up
In the dashboard, open Settings, Notification channels, and the Off-host heartbeat card. Paste the ping URL and save. The URL is stored encrypted, and afterwards only its host is shown. On the server, the environment variables areAPP_HEARTBEAT_URL, APP_HEARTBEAT_FAIL_URL and APP_HEARTBEAT_INTERVAL (default 60s, at least 5s). When APP_HEARTBEAT_URL is set, the dashboard card becomes read-only. An invalid URL stops the control plane at boot with a message that names the variable.
The full variable list is on the Off-host heartbeat page.
Pair it with a monitor
Any monitor that accepts a dead man’s switch ping works the same way:- Create a check or heartbeat monitor in your monitoring service.
- Set its expected period a little above
APP_HEARTBEAT_INTERVAL. - Set its grace period to a few minutes, so one slow ping does not page you.
- Paste its ping URL into Levelrail.
- For the failure URL, use the monitor’s failure or down endpoint if it has one.
Redirects, private addresses and logs
Redirects to another host are refused, and at most three redirects are followed on the same host. Private and loopback destinations are blocked unlessAPP_HEARTBEAT_ALLOW_PRIVATE=1 is set, which is useful for a monitor on your LAN. The URL never appears in logs or API responses. Only its host does.
Per-job heartbeats
A scheduled job can ping its own URL, so a monitor alerts when a nightly job stops or fails. Set the URL per job; see Scheduled jobs.- A successful job sends a GET to the job’s ping URL.
- A failed, timed out, interrupted or container-not-running job sends a GET to the fail URL. If none is set, the fail URL is the ping URL with
/failadded to the path, and the query string is kept. - A skipped job, or one replaced by a newer one, sends nothing, so the monitor’s grace period decides.
- A transient failure (a network error or a 5xx answer) is retried with backoff. A 4xx answer is final.
- A ping never changes the job result.
Public status page
The status page is off until you enable it. It lists only the components you choose, under public names you choose. A visitor sees the overall state, each component’s status, a 90 day uptime bar, active and recent incidents, and scheduled maintenance. The page itself is server-rendered HTML with no JavaScript. Turn it on and add a component from the CLI:/public/status on the control plane host. The same data is available at /public/status.json, /public/status.rss and /public/status.atom. For a status domain of its own, use status-page set --domain HOST, then point that DNS record at the control plane.
Component kinds decide how status is measured:
Uptime bars come only from samples the control plane records while the page is enabled, one per component per
APP_STATUS_PAGE_SAMPLE_INTERVAL (default 1m). There is no backfill, so days before you enabled the page show as “no data”.
What the public view includes
The public view is a fixed list: the page title and description, component public names, statuses, uptime figures, and the incident and maintenance text you write. It never includes check URLs, app names behind a component, hostnames, error messages, internal IDs, node names or metrics.Tuning and limits
Public reads are rate limited per client IP.APP_STATUS_PAGE_RATE_LIMIT_RPM defaults to 120. The built view is cached for APP_STATUS_PAGE_CACHE_TTL (default 30s). Each probe times out after APP_STATUS_PAGE_CHECK_TIMEOUT (default 8s).
The rate limit uses the TCP peer address and does not trust X-Forwarded-For. Behind a reverse proxy, every visitor shares the proxy’s budget, so raise the limit or cache at the proxy.
Uptime is measured from this one control plane, not from several regions, and covers only the time the page has been enabled. There are no email or webhook subscriptions to the page itself, so the RSS and Atom feeds are the push channel. Signed webhooks for admin-created subscribers are described on the Public status page.
A first-week check
Do these once after setup, from a machine that is not the server:- Set the heartbeat URL and confirm the monitor shows a ping within two intervals. pending run
- Stop the control plane on a test install and confirm the monitor alerts after its grace period. pending run
- Fail the database or stall the reconcile loop on a test install and confirm pings stop, or that the failure URL is called. pending run
- Enable the status page, add one
checkcomponent, wait a few minutes and open/public/statusand/public/status.json. pending run - Load
/public/statusthrough your custom status domain and confirm TLS is issued. pending run
What this does not cover
- No notification to your users when the page changes, beyond the feeds.
- No per-app alerting from the heartbeat. Use your app alerts for that.
- No ping from a node when the control plane is up but a node is unhappy. Node silence pings cover only the case where a node loses contact with the control plane. See Off-host heartbeat.
Related
Off-host heartbeat
Full variable list, the health gate and the node silence URL.
Public status page
Components, incidents, maintenance, feeds, webhooks and branding.