Docs/ Operations/ Monitoring & Metrics

Monitoring and Metrics

sh0 samples the server and every app container every 10 seconds and keeps the last hour. Alerts and uptime checks tell you when something goes wrong.

What is collected

A collector reads Docker's statistics and the host's figures every 10 seconds and stores them in sh0's own database. Nothing is sent outside the server.

  • Server -- CPU, memory used, memory percentage, memory total, disk usage.
  • Each running app container -- CPU, memory used, memory as a percentage of its limit, network received and sent.

Retention

Metrics are kept for one hour. Older samples are deleted by a pruning pass that runs every hour. On a running server, the last hour of a series therefore holds about 360 samples (actual output):

Terminal
$ curl -s -H "Authorization: Bearer $SH0_TOKEN" \
    "https://panel.example.com/api/v1/metrics?metric_type=server_cpu" \
  | jq -c '{n:(.data|length), oldest:([.data[].recorded_at]|min), newest:([.data[].recorded_at]|max)}'
{"n":360,"oldest":"2026-10-02 02:10:27","newest":"2026-10-02 03:10:17"}

Both values are options of sh0 serve: --metric-interval (seconds, 0 disables collection) and --metric-retention (seconds, default 3600). To change them, edit the ExecStart line of the sh0 service and restart it.

Note
sh0 is not a long-term metrics store. For history over days or weeks, run a dedicated tool (Prometheus with cAdvisor or node_exporter, Netdata, your provider's monitoring) next to it.

In the dashboard

  • Monitoring, Overview tab -- current CPU, memory, disk and network of the server.
  • Apps tab -- the running apps with their CPU and memory, sortable.
  • Uptime tab -- uptime checks and their incidents (Pro license).
  • Alerts tab -- alert rules, with Create Alert and Test (Pro license).

Metrics API

The same figures through the API (actual response for an app limited to 256 MB):

Terminal
$ curl -s -H "Authorization: Bearer $SH0_TOKEN" https://panel.example.com/api/v1/apps/<app-id>/stats
{"data":{"cpu_percent":0.0,"memory_bytes":8355840,"memory_limit_bytes":268435456,"memory_percent":3.11279296875,"network_rx_bytes":3124,"network_tx_bytes":542}}
  • GET /api/v1/metrics/server -- current server figures.
  • GET /api/v1/apps/<id>/stats -- current figures of an app, as above; GET /api/v1/apps/stats for every app at once; GET /api/v1/apps/<id>/metrics -- the stored samples of an app.
  • GET /api/v1/metrics -- stored samples, filtered by app_id, metric_type (cpu, memory, memory_percent, network_rx, network_tx, server_cpu, server_memory, server_memory_percent, server_disk…) and since.

Alerts

Alerts require a Pro license or higher. Create them from Monitoring, Alerts, or through the API. channel_config is a JSON object passed as a string (actual response):

Terminal
$ curl -X POST https://panel.example.com/api/v1/alerts \
    -H "Authorization: Bearer $SH0_TOKEN" -H "Content-Type: application/json" \
    -d '{"app_id":"<app-id>","alert_type":"high_memory","threshold":90,"channel":"webhook","channel_config":"{\"url\":\"https://hooks.example.com/sh0\"}"}'
{"data":{"alert_type":"high_memory","app_id":"<app-id>","channel":"webhook","channel_config":"{\"url\":\"https://hooks.example.com/sh0\"}","created_at":"2026-10-02 03:10:54","enabled":true,"id":"ca506b3d-b91a-4ca3-941e-be42c6ad2ef3","last_fired":null,"threshold":90.0}}

An alert with an app_id needs the developer role on that app's project; an alert without one is server-wide and needs the admin or owner role. Readers with the viewer role see the alert without its channel_config, which holds the credentials.

Alert types

  • high_cpu, high_memory -- the latest sample exceeds threshold (a percentage). Evaluated every 30 seconds; an alert that fired stays quiet for 5 minutes.
  • app_down -- no CPU sample for the app in the last 30 seconds, which happens when its container is not running.
  • disk_full -- checked every 15 minutes against the alert's threshold, or 80% without one; the same warning is not re-sent for 6 hours. Above 90%, deployments are refused.
  • deploy_failed -- sent when a deployment of the app fails.
  • uptime, uptime_down, uptime_up -- sent by uptime checks (below).
  • ssl_expiry -- Since 1.10.3: fires when a certificate of the alert's app (or of any domain, without an app) expires in fewer days than the threshold, 14 by default. The threshold is a number of days, not a percentage. Evaluated against the certificate sh0 holds on disk; *.sh0.app addresses and certificates you uploaded yourself are not covered. Sent at most once a day per alert.

Notification channels

  • email -- recipients, a list of addresses. Email goes through the SMTP server configured for the sh0 service with the SH0_SMTP_HOST, SH0_SMTP_PORT (587 by default), SH0_SMTP_USER, SH0_SMTP_PASSWORD, SH0_SMTP_FROM and SH0_SMTP_TLS environment variables; without them, no email is sent.
  • slack, discord -- webhook_url, the incoming-webhook URL of the channel.
  • telegram -- bot_token and chat_id.
  • webhook -- url, optional secret and headers. sh0 sends a JSON POST; with a secret, the X-sh0-Signature: sha256=… header carries an HMAC of the body.

Test (POST /api/v1/alerts/<id>/test) sends a sample notification through the configured channel; it is limited to 10 tests per minute per user.

Uptime checks

An uptime check requests a URL at a fixed interval and opens an incident when it fails several times in a row. Create one from the app's Uptime tab or from Monitoring: method GET or HEAD, interval from 10 to 86,400 seconds, timeout from 1 to 30 seconds, expected status code and failure threshold. Uptime checks require a Pro license; their notifications go through the alerts of type uptime.