Self-host Uptime Kuma
A little lookout for your websites. Green bars encouraged.
Shelley handles the setup. About 10 minutes.
Prefer a terminal?
curl -fsSL https://hub.exe.dev/deploy/uptime-kuma/prompt.txt | ssh exe.dev new --prompt=/dev/stdinIs your site up? Uptime Kuma keeps asking so you don't have to. Give it a URL, pick an alert, and let your VM do the worrying.
What you get#
- Uptime Kuma 2, ready for your first monitor.
- A private dashboard and a separate status-page preview.
- Nightly backups, with monitoring set to restart after a reboot.
What's running, and where
https://my-uptime-kuma.exe.xyz:8000/
Your dashboard, whether or not you publish a status page
https://my-uptime-kuma.exe.xyz:8080/
Status page preview
https://my-uptime-kuma.exe.xyz/
Your status page, only after you choose to publish it
Deploy with Shelley#
Click Deploy and Shelley will set up Uptime Kuma on a new VM, then check that it works. No config-file wrangling required.
Prefer a terminal?
curl -fsSL \
https://hub.exe.dev/deploy/uptime-kuma/prompt.txt \
| ssh exe.dev new --prompt=/dev/stdinWhat Shelley is told
Set up Uptime Kuma on this VM by following the exe.dev recipe at https://hub.exe.dev/deploy/uptime-kuma.md from top to bottom.
The recipe's example VM name is my-uptime-kuma. This VM's name is the output of `hostname`: use it wherever the recipe writes my-uptime-kuma. Skip the step that creates the VM; you are on it.
You are done when all of these are true. Check each one yourself:
- Uptime Kuma 2, ready for your first monitor.
- A private dashboard and a separate status-page preview.
- Nightly backups, with monitoring set to restart after a reboot.
Read the whole recipe, including closed details, and follow "Install by
hand" first. The opening walkthrough is for the owner afterwards.
Do not create the Kuma account, add monitors, or send email. Leave public
sharing off. Do not run the later Restore or Update instructions.
Work as exedev in ~/uptime-kuma. Verify docker.service is enabled, both
containers restart unless-stopped, the dashboard responds on localhost:8000,
and /dashboard, /socket.io/, and /metrics return 404 on localhost:8080.
Check that the backup timer is enabled and its first archive passes gzip -t.
The status page will not exist until the owner creates it.
Finish with the private dashboard URL on port 8000 and invite the owner
to create their account and add their first monitor.
Finish with the URLs I should open.
Also at /deploy/uptime-kuma/prompt.txt. The recipe it follows is /deploy/uptime-kuma.md.
Get your first green bar#
Open the dashboard URL Shelley gives you, like
https://my-uptime-kuma.exe.xyz:8000/. Sign in to exe.dev, then create your
Uptime Kuma admin account. Save that password somewhere safe.
- Click Add New Monitor and leave the type as HTTP(s).
- Give it a name, paste your public site's URL, and Save.
- Wait about a minute. Green and Up? You're watching.
Get a heads-up#
Open Settings → Notifications → Set Up Notification. Pick a service you actually read—Telegram, Discord, Slack, or email.
Turn on Default enabled and Apply on all existing monitors, then press Test. Check that the message arrives and Save. Green bars are nice; knowing when they turn red is the point.
Email me, without an SMTP server
Choose Webhook to use exe.dev's email gateway:
- Post URL:
http://169.254.169.254/gateway/email/send - HTTP Method:
POST - Request Body: Custom Body
Paste this body, replacing you@example.com with your exe.dev email
address:
{"to":"you@example.com","subject":"Uptime Kuma alert","body":{{ msg | json }}}
Enable Additional Headers and paste:
{"Content-Type":"application/json"}
Then use Test and Save as above. No SMTP password to dig up.
Share a status page (optional)#
In Status Pages, choose New Status Page. Give it a name, use
status as the slug, then click Next. Add a group, select only the
monitors you want visitors to see, and Save.
Preview it at https://my-uptime-kuma.exe.xyz:8080/. Publishing makes all
your status pages public, so review their monitor lists first. Ready?
On your computer, using your VM's name:
ssh exe.dev share port my-uptime-kuma 8080
ssh exe.dev share set-public my-uptime-kuma
https://my-uptime-kuma.exe.xyz/ now shows your status page. The dashboard
stays private on port 8000. To unpublish, run
ssh exe.dev share set-private my-uptime-kuma on your computer.
Keep your history#
Your monitors and history live in ~/uptime-kuma/data. Nightly backups go
to ~/uptime-kuma/backups. Keep a copy off the VM—a backup on a lost VM
won't help much. On your computer:
scp -r my-uptime-kuma.exe.xyz:uptime-kuma/backups .
Backups include notification credentials, so keep them private. For updates or hiccups, ask Shelley—or open the instructions below.
The nuts and bolts#
Happy clicking Deploy? You can stop here. These are Shelley's setup steps and your maintenance reference.
Install by hand
Create a VM#
On your computer (pick another name if my-uptime-kuma is taken):
ssh exe.dev new --name=my-uptime-kuma
ssh my-uptime-kuma.exe.xyz
All remaining installation commands run on the VM, as exedev.
Start Uptime Kuma#
Enable Docker at boot so monitoring comes back after a reboot:
sudo systemctl enable --now docker.service
mkdir -p ~/uptime-kuma && cd ~/uptime-kuma
cat > compose.yaml <<'EOF'
name: uptime-kuma
services:
uptime-kuma:
image: louislam/uptime-kuma:2
restart: unless-stopped
environment:
UPTIME_KUMA_DB_TYPE: sqlite
ports: ["8000:3001"]
volumes: ["./data:/app/data"]
status-proxy:
image: nginx:stable-alpine
restart: unless-stopped
ports: ["8080:8080"]
volumes: ["./status-proxy.conf:/etc/nginx/conf.d/default.conf:ro"]
depends_on: [uptime-kuma]
EOF
cat > status-proxy.conf <<'EOF'
server {
listen 8080;
absolute_redirect off;
resolver 127.0.0.11 valid=30s;
set $kuma http://uptime-kuma:3001;
location = / { return 302 /status/status; }
location ~ ^/(status/|api/status-page/|api/badge/|assets/|upload/) {
limit_except GET HEAD { deny all; }
proxy_pass $kuma;
proxy_set_header Host $host;
}
location ~ ^/[^/]+\.(svg|png|ico|json|txt|js)$ {
limit_except GET HEAD { deny all; }
proxy_pass $kuma;
}
location / { return 404; }
}
EOF
docker compose up -d --wait --wait-timeout 180
docker compose ps
printf 'Dashboard: https://%s.exe.xyz:8000/\n' "$(hostname)"
Both services should be Up, with Kuma healthy. The HTTPS proxy handles HTTPS and exe.dev login. The nginx proxy lets you publish status pages later without exposing the dashboard.
Set up nightly backups#
This copies SQLite safely while Kuma keeps watching, checks the copy, and keeps two weeks of backups:
cat > backup.sh <<'EOF'
#!/bin/sh
set -eu
umask 077
cd "$(dirname "$0")"
mkdir -p -m 700 backups
out="backups/uptime-kuma-$(date +%Y%m%d-%H%M%S).tar.gz"
docker compose exec -T uptime-kuma sh -eu -c '
work=$(mktemp -d)
trap "rm -rf \"$work\"" EXIT
sqlite3 /app/data/kuma.db ".backup $work/kuma.db"
test "$(sqlite3 "$work/kuma.db" "PRAGMA integrity_check;")" = ok
cp -a /app/data/db-config.json /app/data/upload /app/data/docker-tls "$work/"
tar -C "$work" -czf - .
' > "$out.tmp"
mv "$out.tmp" "$out"
find backups -name 'uptime-kuma-*.tar.gz' -mtime +14 -delete
EOF
chmod +x backup.sh
sudo tee /etc/systemd/system/uptime-kuma-backup.service >/dev/null <<'EOF'
[Unit]
Description=Back up Uptime Kuma
After=docker.service
[Service]
Type=oneshot
User=exedev
ExecStart=/home/exedev/uptime-kuma/backup.sh
EOF
sudo tee /etc/systemd/system/uptime-kuma-backup.timer >/dev/null <<'EOF'
[Unit]
Description=Back up Uptime Kuma nightly
[Timer]
OnCalendar=daily
Persistent=true
[Install]
WantedBy=timers.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now uptime-kuma-backup.timer
sudo systemctl start uptime-kuma-backup.service
systemctl list-timers uptime-kuma-backup.timer
gzip -t backups/*.tar.gz
The timer should list its next run; gzip exits silently on success.
You're ready for the dashboard. Create your account and try the walkthrough
above.
Back up and restore
To take an extra backup, on the VM:
cd ~/uptime-kuma
./backup.sh
Restore#
Pick an archive in backups/ and substitute its filename below. On a new
VM, run Install by hand first and copy your archive into that folder.
These commands run on the VM; they set the current data aside rather
than delete it:
cd ~/uptime-kuma
backup=backups/uptime-kuma-YYYYMMDD-HHMMSS.tar.gz
tar -tzf "$backup" >/dev/null &&
sudo systemctl stop uptime-kuma-backup.timer uptime-kuma-backup.service &&
docker compose stop uptime-kuma &&
sudo mv data "data.before-restore-$(date +%s)" &&
mkdir data &&
sudo tar -C data -xzf "$backup" &&
docker compose up -d --wait &&
sudo systemctl start uptime-kuma-backup.timer
Open the dashboard and check your monitors. Your account and notification settings return to how they were when the backup was taken.
Update
On the VM, back up first, then pull and restart:
cd ~/uptime-kuma
./backup.sh && docker compose pull && docker compose up -d --wait
Reopen the dashboard and check your monitors. The 2 image tag keeps you
on Uptime Kuma 2; read the
release notes before
changing major versions.
Something's not happy?
Start with the logs. On the VM:
cd ~/uptime-kuma
docker compose ps
docker compose logs --tail=50
- Down after a reboot? Run
sudo systemctl enable --now docker.service. - No email? Check your spam folder, the webhook's email address and
headers, and the logs for
Cannot send notification. - Forgot your Kuma password? Run
docker compose exec uptime-kuma npm run reset-password. - Status page missing? Its slug must be
status, or you'll need to change/status/statusinstatus-proxy.confand rundocker compose restart status-proxy.
Frequently asked questions#
Is Uptime Kuma free?
Yes—it's open source and MIT-licensed. You pay for your VM; see exe.dev plans.
Can I monitor a private exe.dev app?
Yes, through a VM-to-VM integration. Monitor the integration's URL, not the app's private exe.xyz address; otherwise you may just be checking the exe.dev login page.
Can my status page use my own domain?
Yes. Publish it first, then follow the custom domain guide, or ask Shelley to help.
Who watches the watcher?
Kuma can't alert you while its own VM is offline. Run it separately from the apps it watches; use an outside monitoring service if you need to keep an eye on Kuma itself.
Updated Oct 14, 2026 · Markdown · manifest.json · Something wrong? Tell us