# 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.

Source: https://vpsnine.com/tutorials/self-host-uptime-kuma · Updated: 2026-10-21

Difficulty: beginner · Time: about 20 minutes · Tested on: Ubuntu 24.04 LTS

Prerequisites:

- 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](https://github.com/louislam/uptime-kuma/releases) 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](https://vpsnine.com/help/server-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](https://vpsnine.com/help/backups-you-should-run) 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](https://github.com/louislam/uptime-kuma/wiki/Migration-From-v1-To-v2) 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

- Run more apps behind one proxy: [Docker Compose with a reverse proxy](https://vpsnine.com/tutorials/docker-compose-reverse-proxy).
- Learn what to watch on the server itself in [Monitor your server](https://vpsnine.com/help/monitor-your-server).
- Read Uptime Kuma's own [documentation wiki](https://github.com/louislam/uptime-kuma/wiki).
