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):
$ 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.
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):
$ 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/statsfor every app at once;GET /api/v1/apps/<id>/metrics-- the stored samples of an app.GET /api/v1/metrics-- stored samples, filtered byapp_id,metric_type(cpu,memory,memory_percent,network_rx,network_tx,server_cpu,server_memory,server_memory_percent,server_disk…) andsince.
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):
$ 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 exceedsthreshold(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'sthreshold, 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.appaddresses 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 theSH0_SMTP_HOST,SH0_SMTP_PORT(587 by default),SH0_SMTP_USER,SH0_SMTP_PASSWORD,SH0_SMTP_FROMandSH0_SMTP_TLSenvironment variables; without them, no email is sent.slack,discord--webhook_url, the incoming-webhook URL of the channel.telegram--bot_tokenandchat_id.webhook--url, optionalsecretandheaders. sh0 sends a JSONPOST; with a secret, theX-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.