Self-host Uptime Kuma with Docker and HTTPS

Run Uptime Kuma 2 in Docker Compose, claim it privately over an SSH tunnel, put Caddy in front for HTTPS, then add monitors, alerts and a status page.

Tested on Ubuntu 24.04 LTS

Before you start

  • A server with Docker Engine and the Compose plugin
  • A domain or subdomain for the dashboard and status page
  • Root or a user with sudo
  • SSH access from your computer

To self-host Uptime Kuma, run the louislam/uptime-kuma:2 image with Docker Compose, bind its port 3001 to 127.0.0.1, create the admin account through an SSH tunnel, and then put Caddy in front to serve the dashboard and status page over HTTPS. It runs comfortably on the smallest server: one container, one volume, about fifteen minutes of work.

Uptime Kuma checks websites, TCP ports, DNS records, Docker containers and more on a schedule, and alerts you through email, Telegram, Discord, Slack, ntfy and many other services when something stops answering.

What In this tutorial
The domain status.example.com
The server's public IPv4 address 203.0.113.10
The project directory /opt/uptime-kuma

Put it on the right server

A monitor can't report that its own server is down. Run Uptime Kuma on a different server from the sites it watches, ideally with a different provider or in a different location. If you only have one server, it still catches crashed services, expired certificates and full disks on others, but pair it with an outside check for the server itself.

Pick the image tag

Uptime Kuma 2 is the current release line (2.5.6 at the time of writing). Its Docker image tags are not what you'd guess:

Tag What it runs
louislam/uptime-kuma:latest Version 1, the old line
louislam/uptime-kuma:1 Version 1
louislam/uptime-kuma:2 The newest 2.x release
louislam/uptime-kuma:2.5.6 Exactly 2.5.6

So latest gives you version 1. Use 2 to get every 2.x update when you pull, or an exact version like 2.5.6 to choose when you upgrade. Check the releases page for the newest one.

Run Uptime Kuma with Docker Compose

Create a directory and the Compose file:

bash
sudo mkdir -p /opt/uptime-kuma
cd /opt/uptime-kuma

/opt/uptime-kuma/compose.yaml:

yaml
services:
  uptime-kuma:
    image: louislam/uptime-kuma:2.5.6
    restart: unless-stopped
    ports:
      - "127.0.0.1:3001:3001"
    volumes:
      - uptime-kuma:/app/data

volumes:
  uptime-kuma:

The 127.0.0.1: in front of the port matters. Docker's published ports bypass ufw, so "3001:3001" would put the setup page on the internet, where the first visitor gets to create the admin account. Bound to localhost, only the server itself (and Caddy, later) can reach it.

Start it:

bash
sudo docker compose up -d

Check it worked

bash
sudo docker compose ps
curl -I http://127.0.0.1:3001

After about thirty seconds ps shows the container (healthy), and curl gets HTTP/1.1 302 Found with Location: /setup-database: it is waiting for you to finish the setup.

Claim it over an SSH tunnel

From your own computer, forward a local port to the server's port 3001 through SSH:

bash
ssh -L 3001:127.0.0.1:3001 root@203.0.113.10

Leave that session open and browse to http://localhost:3001.

  1. Database. Uptime Kuma 2 asks where to keep its data first. Choose SQLite: a single file in the volume, nothing else to run or back up. Embedded MariaDB suits very large setups with hundreds of monitors. Use MariaDB/MySQL only if you already run a database server you look after.
  2. Admin account. Create the admin user with a long password.
  3. Two-factor login. Under Settings, Security, turn on 2FA.

Put Caddy in front for HTTPS

You need HTTPS to reach the dashboard without a tunnel and to publish a status page. Point an A record (and an AAAA record, if the server has IPv6) for status.example.com at the server, then install Caddy from its repository:

bash
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update
sudo apt install -y caddy

Replace the contents of /etc/caddy/Caddyfile with:

text
status.example.com {
	reverse_proxy 127.0.0.1:3001
}

Open the web ports and reload:

bash
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo caddy validate --config /etc/caddy/Caddyfile
sudo systemctl reload caddy

Caddy gets a Let's Encrypt certificate and passes the websocket connection the dashboard uses without extra settings. If the server sits behind a panel firewall, allow 80 and 443 there too.

Then, in Uptime Kuma's Settings, Reverse Proxy, set Trust Proxy to Yes, so it logs visitors' real addresses instead of 127.0.0.1, and under General set Primary Base URL to https://status.example.com, so links in notifications point to the right place.

Check it worked

bash
curl -I https://status.example.com

It answers HTTP/2 302 with a redirect to /dashboard. Open the address in a browser and log in.

Add monitors, alerts and a status page

  1. Notifications first. In Settings, Notifications, set up at least one channel and press Test. A monitor without a notification only changes color on a page you're not looking at.
  2. Monitors. Add New Monitor: HTTP(s) for a website (it can also warn before the certificate expires, under Advanced), TCP Port for SSH or a database port, DNS for a record that must not change, Ping for a host. An interval of 60 seconds with two or three retries avoids alerts for one dropped request.
  3. Status page. Status Pages, New Status Page, pick a slug, add the monitors you want to show and save. It's public at https://status.example.com/status/<slug>; the dashboard still needs a login.

Back it up

With SQLite, everything (monitors, history, settings, users) is in the uptime-kuma volume. Compose prefixes the project name, so the volume is called uptime-kuma_uptime-kuma. The database runs in WAL mode, so stop the container for a consistent copy:

bash
cd /opt/uptime-kuma
sudo docker compose stop
sudo docker run --rm -v uptime-kuma_uptime-kuma:/data -v /root:/backup alpine tar czf /backup/uptime-kuma-$(date +%F).tgz -C /data .
sudo docker compose start

That takes a few seconds of downtime. Copy the archive off the server; Back up your server shows how to do it nightly.

Update, and upgrade from version 1

For a new 2.x release, back up, change the tag in compose.yaml, then:

bash
cd /opt/uptime-kuma
sudo docker compose pull
sudo docker compose up -d
sudo docker image prune -f

If you run version 1 today (from :1, :latest, or a VPSNine one-click install made before 2026-10-21; newer ones run :2), version 2 migrates the database on its first start. Back up the volume first. Point the Compose file at your existing volume by declaring it external, using the name docker volume ls shows for it (here, uptime-kuma):

yaml
volumes:
  uptime-kuma:
    external: true

Then remove the old container and start the new one:

bash
sudo docker rm -f uptime-kuma
sudo docker compose up -d
sudo docker compose logs -f

The log shows [MIGRATION] lines while it converts the data. With months of history, that can take a long time; let it finish and don't restart the container halfway. The project's migration notes list the changes.

Troubleshooting

The browser at http://localhost:3001 says the connection was refused. The SSH tunnel isn't open, or something on your computer already uses port 3001. Use another local port: ssh -L 13001:127.0.0.1:3001 root@203.0.113.10 and browse to http://localhost:13001.

The dashboard loads but shows a spinner forever, or "Cannot connect to the socket server". The websocket connection doesn't get through. Caddy handles it without settings; with nginx, add proxy_http_version 1.1 and the Upgrade and Connection headers.

After an upgrade, the page sits on a migration screen. The database migration from version 1 is running. sudo docker compose logs -f shows progress; wait for it.

You lost the admin password. Reset it from the server:

bash
cd /opt/uptime-kuma
sudo docker compose exec uptime-kuma npm run reset-password

Next steps

Be first when orders open

Join the waitlist and we'll send you one email when VPSNine launches. Pick a plan if you already know which one you want, and we'll size the first servers around it.

  • One email, on launch day
  • Monthly or yearly, no lock-in
  • 14-day money-back guarantee