Skip to content

How to Deploy Uptime Kuma on a VPS

Updated Sep 2026

We earn commissions when you shop through the links below. Full disclosure →

Self-host Uptime Kuma on a tiny VPS — one container, one volume, and a monitoring setup that actually pages you when your other servers fall over.

Before you start
  • A small VPS — 1 vCPU / 512 MB–1 GB RAM is plenty
  • A fresh Ubuntu 24.04 or 26.04 server with root/sudo SSH access
  • A domain you can point at the server, on a box separate from what you monitor
  • Docker Engine + Compose installed (see the base guide below)
Need a box for this guide? Kamatera's free tier lets you spin one up now.Start free on Kamatera → (opens in new tab)

What Uptime Kuma is

Uptime Kuma is a self-hosted uptime monitor and status page: it pokes your services on a schedule — HTTP, TCP port, ping, DNS, TLS certificate expiry, Docker containers, and more — records the results, and notifies you through nearly a hundred channels when something stops answering. It's a Node.js app under the MIT licence, rated 1 / 5 to deploy, and it runs in 512 MB of RAM. It is the closest thing on this site to a zero-effort install.

The interesting part of this deploy isn't the install — it's one container and one volume, and you'll be done in ten minutes. The interesting part is everything after: where the monitor lives relative to what it watches, which notification channels actually reach you at 3am, and what you check besides "is port 443 open". Those get most of the words below.

Server sizing — the smallest box on this site

Uptime Kuma stores its history in an embedded database and spends its life making small outbound requests. A monitor checking dozens of endpoints every sixty seconds barely registers on a CPU graph.

  • 512 MB RAM / 1 vCPU — genuinely enough for a personal setup with a few dozen monitors.
  • 1 GB RAM — the comfortable default, with room for the browser-based checks and a longer retention window.
  • 2 GB RAM+ — only if you're running hundreds of monitors at short intervals, or keeping a very long history.

Disk is similarly modest: 10–20 GB covers the app and years of check history at a normal retention setting. This is the one workload where the cheapest plan a reputable provider sells is the right answer — any entry-tier VPS has headroom to spare.

Put it somewhere else — the placement rule

This is the decision that determines whether the deploy is worth anything, so make it before you install: a monitor must not run on the machine it monitors. If your app server dies, so does the monitor on it, and you learn about the outage from a customer instead of a notification.

Practically:

  • Give Uptime Kuma its own small VPS, separate from your application hosts.
  • Prefer a different provider or region from your main workload. A shared data-centre network incident takes out the monitor and the monitored together.
  • If you can, check the monitor itself — Uptime Kuma's own status can be watched by a free external ping service, so a silent monitor doesn't read as "everything is fine".

Because the workload is tiny, this second box costs a few dollars a month. It's the cheapest insurance in your stack.

Where to host itaffiliate disclosure
Hetzner Cloudrun it on
2 vCPU · 4 GB RAM · 80 GB SSD · $23.59/mo
Get Hetzner Cloud (opens in new tab)
Kamaterafree trial
1 vCPU · 1 GB RAM · 20 GB SSD · $4.00/mo
Start free on Kamatera → (opens in new tab)
DigitalOceanalso works on
1 vCPU · 1 GB RAM · 25 GB SSD · $6.00/mo
Deploy on DigitalOcean → (opens in new tab)

Paid link — we earn a commission if you shop through it.

Prepare the server

This guide assumes Docker Engine and the Compose plugin are installed, along with a non-root deploy user and a ufw firewall. If not, work through Docker & Compose on Ubuntu first.

Open only SSH and the reverse proxy ports — Uptime Kuma's own port stays internal:

sudo ufw allow OpenSSH
sudo ufw allow 80
sudo ufw allow 443
sudo ufw enable
sudo ufw status verbose

Outbound traffic matters more than inbound here: the monitor needs to reach what it checks. If your provider has an egress firewall, make sure ICMP is permitted for ping monitors — otherwise every ping check reports down while HTTP checks pass, which is a confusing first hour.

Install Uptime Kuma

One service, one named volume mounted at /app/data. That volume holds the database, the settings, the notification configuration, and the uploaded status page assets — it is the entire application state.

mkdir ~/uptime-kuma && cd ~/uptime-kuma
# docker-compose.yml
services:
  uptime-kuma:
    image: louislam/uptime-kuma:1
    container_name: uptime-kuma
    restart: unless-stopped
    volumes:
      - uptime-kuma:/app/data
    ports:
      # Loopback only — the reverse proxy is the sole public door.
      - "127.0.0.1:3001:3001"

volumes:
  uptime-kuma:

Start it:

docker compose up -d
docker compose logs -f

One storage caveat from upstream that bites people on fancier setups: keep that volume on local disk. Network filesystems such as NFS are not supported, and the embedded database misbehaves on them. A plain Docker volume or a local bind mount is what you want.

HTTPS + domain

Point an A record for status.example.com at the server's public IP and wait for it to resolve, then terminate TLS in front of 127.0.0.1:3001. The simplest path is Automatic HTTPS with Caddy:

status.example.com {
    reverse_proxy 127.0.0.1:3001
}

The one requirement worth naming: Uptime Kuma's dashboard runs over WebSockets. The live-updating monitor list is a socket connection, not polling. Caddy proxies WebSockets transparently, so the block above is complete. On nginx you must forward the upgrade headers, or the page loads and then sits there empty:

location / {
    proxy_pass http://127.0.0.1:3001;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $host;
}

If you run Caddy as a container, 127.0.0.1 is the proxy's own loopback — put both services in one compose file and use reverse_proxy uptime-kuma:3001 instead, dropping the host port publish.

First login and hardening

Load https://status.example.com. On a fresh install, the first screen is a setup form that creates the administrator account — there is no default password, and once that account exists the form is gone. There's no open registration to disable, which is a nice property; the only accounts are ones an admin makes.

Do these in order:

  1. Use a strong, unique password. This dashboard knows your entire infrastructure map — every hostname, port, and internal endpoint you monitor.
  2. Turn on two-factor auth under Profile → Security.
  3. Decide what the status page exposes. Status pages are public by design. Give them friendly service names, not internal hostnames, and don't publish one at all if the service list itself is sensitive.
  4. Keep the container off the public interface — the 127.0.0.1 bind above is what makes the reverse proxy the only route in.

Set up monitors and notifications

An uptime monitor that can't reach you is a dashboard, not an alert. Configure notifications before the monitors, so the first real failure pages you.

Notifications live under Settings → Notifications. Pick at least two independent channels, because the useful failure mode is the one where your main channel is also broken:

  • A chat webhook (Slack, Discord, Mattermost, Telegram) for day-to-day visibility.
  • A push or SMS channel for anything that must wake you up.
  • Email/SMTP as a durable record — but never as the only channel, since a mail outage takes your alerts with it.

Mark a notification as the default so new monitors inherit it, and use each channel's Test button — a misconfigured webhook fails silently otherwise.

Monitors. Add more than "the port is open":

  • HTTP(s) against a real health endpoint, with keyword matching so a 200-with-an-error-page still counts as down.
  • TCP port for databases and services with no HTTP surface.
  • Ping for raw host reachability, to separate "the box is gone" from "the app crashed".
  • TLS certificate expiry — the built-in certificate check on your HTTPS monitors catches the renewal that quietly stopped working.
  • Push monitors for cron jobs and backups: the job calls a URL when it succeeds, and Uptime Kuma alerts when the call doesn't arrive. This is how you find out a nightly backup has been failing for a week.

Set retries and a heartbeat interval that match reality — one missed check on a sixty-second interval is usually noise, three in a row is an incident.

Backups

Everything is in the one volume, which makes this the easiest backup on the site:

docker compose stop
docker run --rm -v uptime-kuma_uptime-kuma:/data -v $(pwd):/backup alpine \
  tar czf /backup/uptime-kuma-$(date +%F).tar.gz -C /data .
docker compose start

Stopping the container first gives a consistent copy of the embedded database — this app is light enough that a few seconds of downtime costs nothing. (The volume's real name is prefixed with the compose project directory; confirm it with docker volume ls if the command above can't find it.)

Copy the archive off the box. And note the irony worth planning around: this server is the thing that tells you other servers died, so it needs its own recovery story. A restore is tar xzf into a fresh volume and docker compose up -d — monitors, history, and notification settings all come back.

Upgrades

cd ~/uptime-kuma
docker compose pull
docker compose up -d

The major-version tag keeps you on the current line and applies schema migrations at startup. Back up first — migrations are one-way. Across a major version, read the project's release notes before pulling, since a major bump can change how the database is stored.

Troubleshooting

Dashboard loads but nothing updates / monitors show as blank. The reverse proxy isn't forwarding WebSockets. Add the upgrade headers (nginx) or use the Caddy block above. Browser dev tools show the failing socket connection immediately.

Every ping monitor is down, HTTP monitors are fine. ICMP is blocked — either by the provider's network firewall or by ufw on the host. Either allow ICMP outbound or switch those checks to TCP port monitors.

A monitor reports down but the site works from your laptop. You're testing from a different network. Check from the monitor's own box: docker compose exec uptime-kuma curl -sI https://example.com. Common causes are DNS resolving differently on the VPS, an IPv6-only AAAA record the server can't reach, or the target's firewall blocking the monitor's IP.

Notifications never arrive. Use the Test button on the notification itself rather than waiting for an outage. Webhook URLs expire and get rotated; SMTP providers reject unauthenticated relays. Also confirm the notification is actually attached to the monitor — a default set after the monitor was created does not apply retroactively.

False alarms every few minutes. The interval is too aggressive or retries are set to 1. Raise retries to 2–3 and lengthen the interval; a monitor that cries wolf gets muted, and a muted monitor is worse than none.

Verification + next steps

You're done when you can: load https://status.example.com over a valid certificate, log in with 2FA, see a monitor go green, deliberately break something (stop a container, block a port) and receive the alert on a real device, then watch it recover. Until you have seen an alert arrive, you have a dashboard, not monitoring.

From there: publish a status page for the services your users care about, add a push monitor to every backup and cron job you own, and pair it with a metrics stack when you want graphs rather than up/down — Deploy Grafana on a VPS covers that side, and Grafana vs Netdata explains how the two fit together. For the ranked host picks, see Best VPS for Monitoring & Uptime.

Next steps

How to self-host Uptime Kuma →More self-hosted monitoring tools →Best VPS for Monitoring & Uptime →Automatic HTTPS with Caddy →Run Claude Code with Ollama on Your Own VPS →Deploy Coolify on a VPS →How to Deploy Actual Budget on a VPS →How to Deploy AnythingLLM on a VPS →How to Deploy Appwrite on a VPS →How to Deploy Audiobookshelf on a VPS →How to Deploy Authelia on a VPS →How to Deploy authentik on a VPS →How to Deploy Baserow on a VPS →How to Deploy Beszel on a VPS →How to Deploy Bitwarden on a VPS →How to Deploy BookStack on a VPS →How to Deploy CapRover on a VPS →How to Deploy Checkmate on a VPS →How to Deploy Directus on a VPS →How to Deploy docker-mailserver on a VPS →How to Deploy Docmost on a VPS →How to Deploy Dokku on a VPS →How to Deploy Dokploy on a VPS →How to Deploy Firefly III on a VPS →How to Deploy Forgejo on a VPS →How to Deploy Gatus on a VPS →How to Deploy Ghostfolio on a VPS →How to Deploy Gitea on a VPS →How to Deploy GitLab on a VPS →How to Deploy GlitchTip on a VPS →How to Deploy Grafana on a VPS →How to Deploy Graylog on a VPS →How to Deploy Headscale on a VPS →How to Deploy Healthchecks on a VPS →How to Deploy Home Assistant on a VPS →How to Deploy Immich on a VPS →How to Deploy Jan on a VPS →How to Deploy Jellyfin on a VPS →How to Deploy Karakeep on a VPS →How to Deploy Keycloak on a VPS →How to Deploy Leantime on a VPS →How to Deploy LibreChat on a VPS →How to Deploy Linkwarden on a VPS →How to Deploy LocalAI on a VPS →How to Deploy Mailcow on a VPS →How to Deploy Mailu on a VPS →How to Deploy Matomo on a VPS →How to Deploy Mattermost on a VPS →How to Deploy Meilisearch on a VPS →How to Deploy Memos on a VPS →How to Deploy n8n on a VPS →How to Deploy Navidrome on a VPS →How to Deploy NetBird on a VPS →How to Deploy Netdata on a VPS →How to Deploy Nextcloud on a VPS →How to Deploy Next.js to a VPS →How to Deploy Nginx Proxy Manager on a VPS →How to Deploy NocoDB on a VPS →How to Deploy ntfy on a VPS →How to Deploy Ollama on a VPS →How to Deploy Open WebUI on a VPS →How to Deploy OpenHands on a VPS →How to Deploy OpenObserve on a VPS →How to Deploy OpenProject on a VPS →How to Deploy Outline on a VPS →How to Deploy Pangolin on a VPS →How to Deploy Paperless-ngx on a VPS →How to Deploy Passbolt on a VPS →How to Deploy Plane on a VPS →How to Deploy Plausible Analytics on a VPS →How to Deploy Pocket ID on a VPS →How to Deploy PocketBase on a VPS →How to Deploy Prometheus on a VPS →How to Deploy Psono on a VPS →How to Deploy Radarr on a VPS →How to Deploy Rocket.Chat on a VPS →How to Deploy SigNoz on a VPS →How to Deploy Sonarr on a VPS →How to Deploy Stalwart on a VPS →How to Deploy Stirling-PDF on a VPS →How to Deploy Supabase on a VPS →How to Deploy Synapse on a VPS →How to Deploy Taiga on a VPS →How to Deploy TeamPass on a VPS →How to Deploy Tinyauth on a VPS →How to Deploy Traefik on a VPS →How to Deploy Trilium on a VPS →How to Deploy Twenty CRM on a VPS →How to Deploy Umami on a VPS →How to Deploy Vaultwarden on a VPS →How to Deploy Vikunja on a VPS →How to Deploy wg-easy on a VPS →How to Deploy Wiki.js on a VPS →How to Deploy Zabbix on a VPS →How to Deploy Zitadel on a VPS →How to Deploy Zulip on a VPS →Docker & Compose on Ubuntu 26.04 →Building AI Workflows with n8n →Install Open WebUI with Ollama →Adding AI-Powered Insights to Plausible Analytics →Building AI-Powered Apps with Supabase and pgvector →

We use analytics cookies (Google Analytics, PostHog) to see which guides are useful. No ad networks, no cross-site tracking. See our privacy policy.