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 GB RAM. Runs comfortably, and the build finishes without a swap file. On 512 MB it works, but add swap before
npm run setupor the build dies. - 1 vCPU. A shared vCPU is fine. Monitoring a few dozen targets is almost entirely idle wait.
- 10 GB disk. The SQLite database and heartbeat history grow slowly; 10 GB holds months of history for a normal setup.
- Ubuntu 24.04 LTS, and a domain you can point at the server (
status.example.combelow). - 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:
Confirm it resolves before you touch TLS. Let's Encrypt validates over HTTP, and a stale record is the most common reason issuance fails:
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:
Check the version. Anything from 20.4 up is fine:
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:
Install Uptime Kuma
Clone the repo into that directory and run the setup, all as the service user:
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:
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:
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
Now prove it is actually up, not just enabled. Three checks:
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.
Create /etc/nginx/sites-available/uptime-kuma:
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:
Issue the TLS certificate
Use certbot's nginx plugin. It edits the config to add the 443 block and the redirect for you:
Certbot installs a systemd timer for renewal. Confirm the whole chain end to end:
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 | |
| Status | systemctl status uptime-kuma |
| Stop / start | sudo systemctl stop uptime-kuma / start |
| Live logs | journalctl -u uptime-kuma -f |
| Backup | sudo tar czf kuma-$(date +%F).tar.gz -C /opt/uptime-kuma/app/data . |
| Upgrade | git 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
- Firewall. Allow SSH and HTTP/HTTPS only. Port 3001 stays private because the app binds to localhost.
- Non-root service user. Done already; the unit runs as
uptime-kuma. - fail2ban on SSH is worth the five minutes on any internet-facing box. Our VPS firewall, SSH and fail2ban guide has the config.
- Off-site backups. A backup on the same VPS dies with the VPS. Ship the
datatarball somewhere else on a schedule. - 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
- Uptime Kuma README (Node >= 20.4, install methods) (2026-09)
- Uptime Kuma Releases (2.5.5, 16 Sept 2026) (2026-09)
- Uptime Kuma Wiki: Reverse Proxy WebSocket headers (unknown)
- SQLITE_BUSY: database is locked (issue #7338) (unknown)
- 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.

