Deploy Uptime Kuma on an Ubuntu VPS with HTTPS

Deploy Uptime Kuma on an Ubuntu VPS with HTTPS

What is Uptime Kuma?

Uptime Kuma is a self-hosted monitoring tool: it pings your sites, TCP ports, DNS records and API endpoints on a schedule and shouts when one stops answering. It replaces paid services like UptimeRobot, Pingdom and StatusCake, and gives you a status page for free. This guide gets it running behind HTTPS on a fresh Ubuntu VPS in one sitting.

I followed Uptime Kuma 2.5.5 (released 16 September 2026) on Ubuntu 24.04 LTS, installed from source with Node.js. Versions and flags below come from the project's own wiki. These guides rot, so check the version you pull before you trust a specific path.

Node or Docker: pick one

Both are supported. Docker is the shorter route if you already run containers, and the project recommends the louislam/uptime-kuma:2 image for that. This tutorial installs from source instead, for one reason: a plain Node process with a systemd unit is easier to reason about when something breaks at 2am, and it drops straight into the nginx-and-Let's-Encrypt pattern you already use for everything else on the box. If your server is already a Docker host, run the container and skip to the nginx section. The reverse-proxy traps are identical either way.

What you need

Uptime Kuma is light. Idle, the Node process is small. The memory-hungry part is the one-time build step, where Vite compiles the frontend; on 512 MB it can be killed by the OOM reaper mid-build. So:

  1. 1 GB RAM. Runs comfortably, and the build finishes without a swap file. On 512 MB it works, but add swap before npm run setup or the build dies.
  2. 1 vCPU. A shared vCPU is fine. Monitoring a few dozen targets is almost entirely idle wait.
  3. 10 GB disk. The SQLite database and heartbeat history grow slowly; 10 GB holds months of history for a normal setup.
  4. Ubuntu 24.04 LTS, and a domain you can point at the server (status.example.com below).
  5. Root or sudo.

This is one of the rare workloads that genuinely fits the smallest plan. Our 1 GB Netherlands VPS in Naaldwijk covers it with headroom, and its AMS-IX and DE-CIX peering gives your checks a clean vantage point onto the public internet. Do not size up for this app alone.

Point the domain at the server

Create an A record (and AAAA if you run IPv6) for the hostname you will use:

status.example.com. A 203.0.113.10
status.example.com. AAAA 2001:db8::10

Confirm it resolves before you touch TLS. Let's Encrypt validates over HTTP, and a stale record is the most common reason issuance fails:

$ dig +short status.example.com
203.0.113.10

Update the system and install dependencies

Ubuntu 24.04 ships Node 18 in its repos, which is below Uptime Kuma's floor of Node.js >= 20.4. Add the NodeSource 20.x repo instead:

sudo apt update && sudo apt upgrade -y
sudo apt install -y git build-essential curl
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt install -y nodejs

Check the version. Anything from 20.4 up is fine:

$ node --version
v20.18.1

build-essential matters: Uptime Kuma compiles a native SQLite module during setup, and without a compiler the install fails with node-gyp errors.

Create the service user and directory

Do not run this as root. Make a dedicated, login-less user and put the code under /opt:

sudo useradd --system --create-home --home-dir /opt/uptime-kuma --shell /usr/sbin/nologin uptime-kuma

Install Uptime Kuma

Clone the repo into that directory and run the setup, all as the service user:

sudo -u uptime-kuma git clone https://github.com/louislam/uptime-kuma.git /opt/uptime-kuma/app
cd /opt/uptime-kuma/app
sudo -u uptime-kuma npm run setup

npm run setup installs dependencies and builds the frontend. It takes a couple of minutes and prints a lot. On a 512 MB box, watch for a line ending in Killed: that is the OOM killer, and it means you need swap. Add a gigabyte and rerun:

sudo fallocate -l 1G /swapfile && sudo chmod 600 /swapfile
sudo mkswap /swapfile && sudo swapon /swapfile

Write the systemd unit

The README suggests PM2. A systemd unit is cleaner: no extra global npm package, and the service is managed the same way as everything else on the host. Bind it to localhost so nothing but nginx can reach it. Create /etc/systemd/system/uptime-kuma.service:

[Unit]
Description=Uptime Kuma
After=network.target

[Service]
Type=simple
User=uptime-kuma
WorkingDirectory=/opt/uptime-kuma/app
ExecStart=/usr/bin/node server/server.js
Environment=UPTIME_KUMA_HOST=127.0.0.1
Environment=UPTIME_KUMA_PORT=3001
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target

Binding to 127.0.0.1 is deliberate. Left on the default, Uptime Kuma listens on every interface, and port 3001 would be reachable from the internet without TLS. We terminate HTTPS at nginx and keep the app private.

Start and verify

sudo systemctl daemon-reload
sudo systemctl enable --now uptime-kuma

Now prove it is actually up, not just enabled. Three checks:

$ systemctl is-active uptime-kuma
active

$ sudo ss -ltnp | grep 3001
LISTEN 0 511 127.0.0.1:3001 0.0.0.0:* users:(("node",pid=1423,fd=20))

$ curl -so /dev/null -w "%{http_code}\n" http://127.0.0.1:3001
200

A 200 from localhost and a listener bound to 127.0.0.1:3001 means the app is healthy. If systemctl is-active says failed, read journalctl -u uptime-kuma -n 50 before going further.

Browserstatus.example.comnginx:443 TLSUptime Kuma127.0.0.1:3001SQLiteHTTPSproxy_passWebSocket (needs the Upgrade headers)

The path every request takes once this tutorial is finished. The dashed line is the WebSocket connection that keeps the dashboard live — it uses the same proxy, which is why a missing Upgrade header freezes the graphs while the pages still load.

Put nginx in front

Install nginx and drop a site config. This is the step people get wrong, and the failure is silent, so read the next section carefully.

sudo apt install -y nginx

Create /etc/nginx/sites-available/uptime-kuma:

server {
listen 80;
server_name status.example.com;

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;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 90s;
}
}

The three lines that matter are proxy_http_version 1.1, proxy_set_header Upgrade $http_upgrade and proxy_set_header Connection "upgrade". The project's own words: "Unlike other web apps, Uptime Kuma is based on WebSocket. You need two more headers Upgrade and Connection in order to accept WebSocket on a reverse proxy." Enable the site and reload:

sudo ln -s /etc/nginx/sites-available/uptime-kuma /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx

Issue the TLS certificate

Use certbot's nginx plugin. It edits the config to add the 443 block and the redirect for you:

sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d status.example.com --redirect --agree-tos -m you@example.com

Certbot installs a systemd timer for renewal. Confirm the whole chain end to end:

$ curl -sI https://status.example.com | head -n1
HTTP/2 200

First login

Open https://status.example.com. The first visit shows a one-time setup screen; create the admin account there. There is no default password, and no way to skip this, so do it before you point anyone else at the URL. Then add your first monitor: + Add New Monitor, type HTTP(s), give it a friendly name and the URL, set the interval, save. The heartbeat bar starts filling immediately. If it does not, jump to the WebSocket trap below.

For notifications: Settings → Notifications → Setup Notification. Uptime Kuma ships with a long list of channels; Telegram is the quickest to prove. Create a bot with @BotFather, paste the token, click Get Update to fetch your chat ID, then Test. Assign the notification to the monitor. Now pause the monitored service for a moment and confirm the alert lands.

Management commands

TaskCommand
Statussystemctl status uptime-kuma
Stop / startsudo systemctl stop uptime-kuma / start
Live logsjournalctl -u uptime-kuma -f
Backupsudo tar czf kuma-$(date +%F).tar.gz -C /opt/uptime-kuma/app/data .
Upgradegit checkout <tag> && npm run setup, then systemctl restart uptime-kuma

The database lives in /opt/uptime-kuma/app/data. That directory is the entire backup; copy it and you have copied everything, monitors, history and settings. For upgrades, stop the service, pull the new tag, rerun setup, restart. Read the release notes first, because a major version can migrate the database on first start.

Production hardening

  1. Firewall. Allow SSH and HTTP/HTTPS only. Port 3001 stays private because the app binds to localhost.
sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw enable
  1. Non-root service user. Done already; the unit runs as uptime-kuma.
  2. fail2ban on SSH is worth the five minutes on any internet-facing box. Our VPS firewall, SSH and fail2ban guide has the config.
  3. Off-site backups. A backup on the same VPS dies with the VPS. Ship the data tarball somewhere else on a schedule.
  4. Monitor the monitor. A single Uptime Kuma instance cannot tell you it is down. Point a free external check, or a second Kuma elsewhere, at your status page.

Where this can bite you

Three failures cause almost every support thread.

1. Missing WebSocket headers: the dashboard silently freezes

This is the big one. If you proxy Uptime Kuma without Upgrade and Connection, the page loads fine, you log in fine, and then nothing updates. Heartbeats stop, response-time graphs flatline, and there is no error anywhere. It looks like the app is broken; it is the proxy dropping the WebSocket. If your dashboard loads but never refreshes, this is your bug, every time. The three directives in the nginx block above fix it. Reload nginx and reload the page.

2. Checking from the same server you are monitoring

If Uptime Kuma runs on the same host, or the same rack, as the thing it watches, it cannot see the outages that matter. A monitor pointed at localhost or a private IP will report "up" while the public site is unreachable, because the network path that failed is not the path being tested. Worse, a whole-server or whole-network outage takes the monitor down with the target, so no alert ever fires. Run the monitor somewhere independent of the infrastructure it watches. Different provider, or at least a different region and network, from your production servers.

3. SQLite locking under many monitors

Uptime Kuma stores everything in SQLite by default, and SQLite serialises writes. Pile on enough monitors at short intervals and writes start colliding, throwing SQLITE_BUSY: database is locked (see the project's issue #7338). The visible symptom is nasty: false "down" alerts for services that were never down. How few is a few? The reporter in that issue hit it at 53 monitors on 60-second intervals, so treat a few dozen as the point to start watching rather than a hundred. From there, either lengthen the check intervals or move to the MariaDB backend that version 2 added for exactly this. Version 2 is a major release: back up first, follow the project's migration guide, and expect the SQLite-to-MariaDB move itself to be manual. Do not chase phantom outages that are really a locked database.

Choosing the plan and region

Uptime Kuma is a 1 GB job. Do not let anyone upsell you for it. What decides the region is trap number two: put the monitor on a network that is independent of what it watches, so a failure at your production site does not blind the very tool meant to catch it.

We run VPS in three places, and each is a fair vantage point depending on where your services live. If your production is in the UK, watch it from Sweden or the Netherlands, not from the same city. The Netherlands VPS in Naaldwijk, peered into AMS-IX and DE-CIX, is a strong neutral watchpoint for European targets. Swedish services pair well with the Sweden VPS in Stockholm, and UK-facing setups can sit on the UK VPS in Coventry as long as the things it monitors do not. Every plan includes IPv4 and IPv6 and always-on DDoS filtering, deploys in under a minute, and upgrades RAM or disk with one reboot if your monitor count ever outgrows the small box. New to the platform? The initial VPS setup checklist covers the pre-flight before you start this guide.

Sources

  1. Uptime Kuma README (Node >= 20.4, install methods) (2026-09)
  2. Uptime Kuma Releases (2.5.5, 16 Sept 2026) (2026-09)
  3. Uptime Kuma Wiki: Reverse Proxy WebSocket headers (unknown)
  4. SQLITE_BUSY: database is locked (issue #7338) (unknown)
  5. Uptime Kuma 2.0: Monitoring tool now with MariaDB support (2025-10)

Frequently asked questions

Which Node.js version does Uptime Kuma need on Ubuntu?

Node.js 20.4 or newer. Ubuntu 24.04's repo Node is 18, which is too old, so add the NodeSource 20.x repository before installing. This guide was tested against Uptime Kuma 2.5.5 on Node 20.18.

Why does my Uptime Kuma dashboard load but never update?

Your reverse proxy is dropping the WebSocket connection. Uptime Kuma pushes heartbeats over WebSocket, so nginx must carry proxy_http_version 1.1 plus the Upgrade and Connection headers. Add those three directives and reload nginx; the live graphs will start moving again.

Can Uptime Kuma run on a 512 MB VPS?

Yes, at runtime it idles well under the limit. The catch is the one-time build during npm run setup, which can be killed by the OOM reaper on 512 MB. Add a 1 GB swap file before setup, or use a 1 GB plan and skip the workaround.

Should I use SQLite or MariaDB?

SQLite is fine for up to a few dozen monitors. With many monitors at short intervals you can hit SQLITE_BUSY errors that show up as false down alerts; issue #7338 reports it at 53 monitors on 60-second intervals. From a few dozen upwards, lengthen intervals or switch to the MariaDB backend added in version 2.

Why shouldn't I run the monitor on the same server it watches?

Because a whole-server or whole-network outage takes the monitor down with the target, so no alert fires, and checks against localhost report up while the public site is unreachable. Run Uptime Kuma on infrastructure independent of what it monitors.

Related reading

Tutorials

WordPress on a UK VPS: a speed and security checklist

A command-level checklist for migrating a small agency's WordPress site off shared hosting onto a UK VPS you now run yourself: PHP-FPM sized from RAM, OPcache and Redis, a page cache that never leaks a cart, TLS and HTTP/2, the security layer that carries real weight, and keeping personal data under UK GDPR.

Read more →
Deploy your server ← Back to blog