Use case · Monitoring

Uptime Kuma self-hosted, placed outside the infrastructure it watches

To run Uptime Kuma self-hosted, start one Docker container from the louislam/uptime-kuma:2 image, keep /app/data on local disk, and put the web UI on port 3001 behind a reverse proxy that passes WebSocket upgrades. The project publishes no CPU or memory minimum, so the machine question is really a placement question: the monitor has to stay up when the thing it watches goes down, which means a separate machine on a separate failure domain.

See the VM flavors

Why the monitor lives on a different machine

A monitor running on the host it watches dies with that host, and a dead monitor sends no alert. The same holds one level up: a probe inside the same rack, the same hypervisor or the same upstream link shares the fault it is meant to report. The useful arrangement is the reverse of convenient. Uptime Kuma sits on its own small machine, reaches your services over the public internet the way your users do, and pages you from there.

That is why a small VM suits it better than a corner of the production box. Uptime Kuma is one Node.js process with an embedded database. It does not need a whole machine's cores; it needs to be somewhere else, with a separate power state, address and route out.

What Uptime Kuma needs

ItemFrom the project's README and wiki
Docker imagelouislam/uptime-kuma:2 (version 2 also has slim and rootless tags)
Without DockerMajor Linux distributions or Windows 10 / Server 2012 R2 x64; Node.js >= 20.4 plus Git, with pm2 keeping the process alive
Not supportedFreeBSD, OpenBSD and NetBSD for the non-Docker install; NFS for the data directory
Storage/app/data on a local directory or volume; SQLite needs working POSIX file locks
DatabaseSQLite or, from version 2, MariaDB (embedded or external)
NetworkTCP 3001 for the UI; outbound access to everything it checks and every alert service

No RAM or core figure appears in the official documentation, so none appears here. Load grows with the number of monitors and how often each runs. Start on a small flavor, watch memory with docker stats for a week, and resize if it climbs.

The VM that fits

Our Cloud VPS is a KVM guest on OpenStack hosts that Server Room operates in New York. Every flavor gets root, an SSH key installed at build, a noVNC console from the Virtual Machines tab, and a choice of four Linux distributions (Ubuntu, Debian, Rocky Linux, AlmaLinux) built from official cloud images. The Docker one-click app installs Docker Engine and the Compose plugin from Docker's repository on first boot, which is the whole prerequisite for the steps below.

  • Shared vCPU is fine here. A monitor spends most of its life waiting on network replies; it is not the all-day CPU load the service page warns about.
  • Egress is metered, ingress is not. Probes and alerts are small requests. On the monthly and yearly flavors, egress past the monthly allowance is shaped to 10 Mbit/s rather than cut, and the machine keeps running. (Elastic flavors bill egress per GB instead.)
  • Mail alerts work from first boot. New machines have outbound port 25 blocked, but 465 and 587 pass, which is what an SMTP relay or mail provider uses.
  • Resizing only grows. Support moves the VM to a bigger flavor with one restart, keeping its address and data; a disk cannot shrink, so start small.

Installing with Docker Compose

  1. Order the VM with the Docker app, or install Docker Engine from Docker's own repository on a plain Ubuntu or Debian image.
  2. Create a directory for the stack and write the compose file below. It is the project's own compose.yaml with one change: port 3001 is published on 127.0.0.1 only, as the README suggests for limiting exposure.
  3. Run docker compose up -d in that directory. The container restarts on its own unless you stop it.
  4. Point a DNS record such as status.example.com at the VM. Uptime Kuma does not run under a subdirectory, so it needs a host name of its own.
  5. Create the admin account before the site is public: from your PC run ssh -L 3001:127.0.0.1:3001 root@your-vm, open http://localhost:3001, and create the account there. Then put the reverse proxy and certificate in front (next section).
services:
  uptime-kuma:
    image: louislam/uptime-kuma:2
    restart: unless-stopped
    volumes:
      - ./data:/app/data
    ports:
      # <Host Port>:<Container Port>
      - "127.0.0.1:3001:3001"

Docker's own documentation warns that published container ports bypass ufw: Docker routes that traffic in the nat table before ufw's rules apply. Binding to 127.0.0.1 is what keeps port 3001 off the public interface; a ufw rule alone does not.

The reverse proxy and WebSockets

Uptime Kuma's dashboard talks to the server over a WebSocket, not a REST API, so a proxy that forwards plain HTTP and drops the upgrade leaves you with a page that never loads its data. The wiki's nginx block carries the two headers that matter, Upgrade and Connection:

server {
  server_name status.example.com;

  location / {
    proxy_set_header   X-Real-IP $remote_addr;
    proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header   Host $host;
    proxy_pass         http://127.0.0.1:3001/;
    proxy_http_version 1.1;
    proxy_set_header   Upgrade $http_upgrade;
    proxy_set_header   Connection "upgrade";
  }
}

Install python3-certbot-nginx and run certbot --nginx -d status.example.com; certbot adds the TLS listener and renews the certificate. Caddy does the same with a two-line reverse_proxy 127.0.0.1:3001 site block. Once the UI is reachable only through the proxy, switch Trust Proxy on (Settings, Reverse Proxy) and the logs record real client addresses. Status pages for other domains need the Host header forwarded, which the block above already does.

Alerts, checks and status pages

Monitor types include HTTP(S), HTTP(S) keyword and JSON query, TCP port, ping, DNS record, WebSocket, push (the monitored job calls Uptime Kuma), Steam game servers and Docker containers, with intervals down to 20 seconds. Alerts go to Telegram, Discord, Slack, Gotify, Pushover, SMTP email and more than 90 services in all, plus whatever Apprise covers.

Public status pages cache results for 5 minutes and refresh on that cycle, so they are for customers, not for you. Version 2 sets retries on new monitors to 0; one retry on an HTTP check is usually worth it to avoid a page at 03:00 for a single dropped packet.

Backups, updates and lost passwords

  • Backup: since version 2, copying the data directory is the only supported method; the old JSON export is gone. Stop the container, archive ./data, start it again.
  • Update: docker compose pull then docker compose up -d --force-recreate in the stack directory.
  • v1 to v2: back up data first. The migration aggregates the heartbeat table and must not be interrupted; on slow hardware the wiki says it can take hours.
  • Lost admin password: from the stack directory, docker compose exec uptime-kuma npm run reset-password.

Where this placement stops being independent

The Cloud VPS runs in New York only. If the services you watch are on our dedicated servers in the same city, the monitor shares their region and our network, and an incident at that layer can silence both. For production that matters, run a second Uptime Kuma, or a push monitor, from another provider and region, and alert from each. Check the path from New York to your targets first with the looking glass.

Uptime Kuma is also not a metrics system. It answers up or down, response time and certificate expiry. For time series and dashboards, the same VM menu has a Grafana app, and the other cities we run are where the watched machines themselves can live.

Questions

What port does Uptime Kuma listen on?

TCP 3001 by default, on all interfaces when published with -p 3001:3001. UPTIME_KUMA_PORT and UPTIME_KUMA_HOST change the port and bind address. With a reverse proxy in front, bind the published port as 127.0.0.1:3001:3001 so only the proxy reaches it.

How much RAM and CPU does an Uptime Kuma server need?

No official figure exists. It is a single Node.js process, and its load scales with the number of monitors and their intervals. A small VM is a reasonable start; measure with docker stats and move to a larger flavor if memory climbs.

Why does the dashboard load but show no data behind nginx?

The WebSocket upgrade is not reaching the app. Add proxy_http_version 1.1 and the Upgrade and Connection headers to the location block. In Nginx Proxy Manager, enable WebSockets Support.

Can I put Uptime Kuma on NFS or a network share?

No. The README states NFS is not supported, and the wiki explains that SQLite needs POSIX file locks to avoid corruption. Map /app/data to a local directory or Docker volume.

Can it send email alerts from a VM with port 25 closed?

Yes. Configure the SMTP notification against your mail provider over submission port 587 or implicit-TLS port 465; new Cloud VPS machines allow both. Only direct delivery on port 25 is blocked by default.

Should the monitor run on the same server as my app?

Not if you want to hear about outages. A monitor that shares a host, network or region with its target fails together with it and cannot raise the alert. Put it on a separate machine, ideally on a separate network.

Put the monitor somewhere else

A small KVM VM in New York with root and Docker on first boot.

See the VM flavors

Related on Server Room

Other use cases