Uptime Kuma is a self-hosted monitor. It checks your sites and services and tells you when they go down. It's also one of the most popular self-hosted tools around. On October 14, 2026, the GitHub repository had about 92,400 stars, and the louislam/uptime-kuma image had been pulled more than 192 million times from Docker Hub.
This guide is the setup I actually ran, plus the problems that come up again and again in the project's GitHub issues.
TL;DR:
- Start
louislam/uptime-kuma:2.5.6with the official compose file. If a reverse proxy goes in front, bind the port to127.0.0.1:3001, and either way create the admin account right after the first start. - Version 2 asks you to pick a database first. SQLite is the simplest choice for one small instance. Keep
./dataon a local disk, not NFS or SMB. - Inside the container,
localhostis the container itself. Monitor services on the host through the Docker network gateway IP orhost.docker.internal. - A notification only fires for monitors it's attached to, so turn on Default enabled or Apply on all existing monitors.
- Behind nginx, pass the
Upgrade,ConnectionandHostheaders, or the WebSocket handshake fails with a 400.
Install Uptime Kuma with Docker Compose
Start the container
Make a folder and save the official compose file in it:
mkdir uptime-kuma && cd uptime-kuma
curl -o compose.yaml https://raw.githubusercontent.com/louislam/uptime-kuma/master/compose.yaml
At the time of writing it contains:
services:
uptime-kuma:
image: louislam/uptime-kuma:2.5.6
restart: unless-stopped
volumes:
- ./data:/app/data
ports:
# <Host Port>:<Container Port>
- "3001:3001"
Watch that port line. "3001:3001" publishes the port on all interfaces (0.0.0.0), and Docker-published ports bypass ufw and firewalld rules. Until you create the admin account, anyone who can reach port 3001 can open the setup wizard, pick the database and make themselves admin. You have two ways to deal with it:
- If you'll put a reverse proxy in front (see the reverse proxy section), change the line to
"127.0.0.1:3001:3001"so only the proxy on the same host can reach it. - If you keep the upstream binding, create the admin account right after you start the container.
Start it and check the log:
docker compose up -d
docker compose logs uptime-kuma
Here is the full first-start log from a fresh, empty ./data (colour codes stripped, nothing else removed):
Welcome to Uptime Kuma
Your Node.js version: 22.22.3
2026-10-14T03:55:30Z [SERVER] INFO: Env: production
2026-10-14T03:55:31Z [SERVER] INFO: Uptime Kuma Version: 2.5.6
2026-10-14T03:55:31Z [SERVER] INFO: Loading modules
2026-10-14T03:55:32Z [SERVER] INFO: Creating express and socket.io instance
2026-10-14T03:55:32Z [SERVER] INFO: Server Type: HTTP
2026-10-14T03:55:32Z [SERVER] INFO: Data Dir: ./data/
2026-10-14T03:55:32Z [SETUP-DATABASE] INFO: db-config.json is not found or invalid: ENOENT: no such file or directory, open 'data/db-config.json'
2026-10-14T03:55:32Z [SETUP-DATABASE] INFO: Starting Setup Database
2026-10-14T03:55:32Z [SETUP-DATABASE] INFO: Listening on:
2026-10-14T03:55:32Z [SETUP-DATABASE] INFO: - http://localhost:3001
2026-10-14T03:55:32Z [SETUP-DATABASE] INFO: - http://172.17.0.2:3001
2026-10-14T03:55:32Z [SETUP-DATABASE] INFO: Waiting for user action...
The ENOENT ... db-config.json line is expected on a first start, because there's no database config yet. The 172.17.0.2 address is the container's own IP and will differ on your machine.
"Waiting for user action" is normal for version 2. It wants you to pick a database in the browser before it starts properly. Keep ./data on a local disk, not on NFS or an SMB share (see database errors).
Pick a database and create the admin account
Open http://<your-server>:3001. Version 2 asks which database to use: Embedded MariaDB, external MariaDB/MySQL, or SQLite.
I picked SQLite. For a single small instance it's the simplest option, and everything lives in ./data/kuma.db. Setup took about 15 seconds, and then the page asked me to create an admin account:
This guide covers SQLite only. I didn't run the two MariaDB options.
Add a monitor and a notification
Add an HTTP monitor
Click Add New Monitor, keep the type as HTTP(s), and fill in a name and a URL. For testing I ran a throwaway nginx container on port 8081 and used a 20-second interval with Retries set to 0, so I could watch the state change quickly. In production, leave retries at 1 or more, or a single dropped packet will page you.
Then I stopped the nginx container. The next check went red with connect ECONNREFUSED 172.18.0.1:8081:
Watch out for Docker networking here. Inside the container, localhost is the container itself. To monitor something that runs on the host, you need an address that points at the host. 172.18.0.1 is the gateway of my compose project's network, and yours will very likely differ. Docker's default bridge is usually 172.17.0.1, and each compose project gets its own subnet. Find yours with:
docker network inspect uptime-kuma_default --format '{{(index .IPAM.Config 0).Gateway}}'
(uptime-kuma_default is <project folder name>_default.) A more portable option, per Docker's compose reference, is to add this to the service in compose.yaml and use host.docker.internal as the hostname in your monitors and webhooks:
extra_hosts:
- "host.docker.internal:host-gateway"
My test used the gateway IP. I didn't repeat it with host.docker.internal.
Attach the notification to your monitors
Go to Settings, then Notifications, then Set Up Notification. There are dozens of providers, such as Telegram, Discord, SMTP and ntfy. To keep the test self-contained, I used the generic Webhook type and pointed it at a small HTTP listener on the host (http://172.18.0.1:9999/kuma, using the gateway IP from above), with Request Body set to application/json.
The Test button worked straight away. My listener got:
{"heartbeat":null,"monitor":null,"msg":"Local webhook Testing"}
This is the part that caught me out. The first time, I saved the notification without turning on Default enabled or Apply on all existing monitors. The test message arrived, but when the monitor went down, nothing was sent. A notification only fires for monitors it's attached to. To fix it, edit the monitor and tick the notification, or re-save the notification with Apply on all existing monitors turned on.
After that, both transitions arrived (trimmed):
{"heartbeat":{"monitorID":1,"status":1,"msg":"200 - OK", ... "retries":0, ...},"monitor":{"name":"demo-site", ...}}
{"heartbeat":{"monitorID":1,"status":0,"msg":"connect ECONNREFUSED 172.18.0.1:8081", ... "retries":1, ...}}
Don't be confused by "retries":1 in the down heartbeat. The monitor's Retries setting really was 0 (I checked the maxretries column in kuma.db). The retries field in the payload is a per-heartbeat failure counter, not the configured setting.
Uptime Kuma sends a notification only when the state changes (up to down, or down to up), not on every failed check. If you want repeat alerts while something stays down, use the resend setting in the monitor form. I left it at 0, which means off.
Put Uptime Kuma behind a reverse proxy
Pass the WebSocket headers
Uptime Kuma's UI talks to the server over Socket.IO. I put nginx in front with a bare proxy_pass and nothing else. On the surface nothing looks wrong. The login page still renders:
But the browser console showed:
WebSocket connection to 'ws://localhost:8090/socket.io/?EIO=4&transport=websocket&sid=...' failed: Error during WebSocket handshake: Unexpected response code: 400
In 2.5.6 the client fell back to HTTP long-polling, so I could still log in. It's slower and less reliable, though. People with more monitors or stricter proxies end up with the "Cannot connect to the socket server" reload loop described in #3884 and #958. This is the full nginx server block that fixed the 400 in my test (plain HTTP on port 8091, because the lab had no domain):
server {
listen 8091;
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-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
On a real server, put the location / { ... } part inside your existing TLS server block (the one with listen 443 ssl;, server_name status.example.com; and your ssl_certificate lines) instead of using listen 8091. I tested only the plain-HTTP block above. If you proxy other apps from the same nginx, the common pattern is a map $http_upgrade $connection_upgrade { default upgrade; '' close; } block in the http context and proxy_set_header Connection $connection_upgrade;. With a proxy in front, also bind the container to 127.0.0.1:3001 as described above.
Prefer a web UI to nginx config files? The Nginx Proxy Manager guide covers the same job, including the Websockets Support toggle this case needs. The Vaultwarden HTTPS guide and the Umami analytics guide use the same 127.0.0.1 binding behind Caddy, and both make good first monitors.
Forward the Host header
proxy_set_header Host $host matters too. Since 1.23.9, Uptime Kuma checks that the WebSocket Origin matches the Host. In #4210, the maintainer's recommended fix is to forward the original Host header (ProxyPreserveHost on for Apache). Setting UPTIME_KUMA_WS_ORIGIN_CHECK=bypass also makes the error go away, but he says plainly that it's "not a good idea, since it is a part of security fix." I didn't reproduce the origin-mismatch error itself, because the 2.5.6 client fell back to polling instead.
Fix common Uptime Kuma errors
Container and port errors
"Bind for 0.0.0.0:3001 failed: port is already allocated"
I reproduced this by starting a second copy:
docker: Error response from daemon: driver failed programming external connectivity on endpoint kuma-second (...): Bind for 0.0.0.0:3001 failed: port is already allocated.
Another container or process already has port 3001. Either stop it (docker ps shows what's using it) or change the host side of the mapping, for example "3002:3001".
Permission denied on /app/data
Reports like #4370 (EACCES: permission denied, mkdir 'data/upload') come from bind-mounting a folder the container user can't write to, often on NAS shares or in rootless setups. The usual fix is to make sure the folder exists and is writable, or to use a named volume. I didn't hit this with the default compose file running as root.
Database and network errors
Data on NFS or network shares, "database is locked"
Keep the SQLite file off NFS. In #2668 a collaborator points to SQLite's own "How To Corrupt" page, and the wiki now says network filesystems aren't supported. Use a local volume.
If you see SQLITE_BUSY: database is locked after upgrading to 2.2.0 or later, read #7289. According to that thread, the maintainer added UPTIME_KUMA_SQLITE_SINGLE_CONNECTION=true in 2.3.0 to bring back the older single-connection behaviour, and slow storage (the thread mentions eMMC) makes the problem worse. I didn't hit SQLITE_BUSY on my test box.
Lots of "timeout of 48000ms exceeded" while the site is up
This is one of the most-commented issues (#275, 100+ comments). In several reports the cause was DNS on the Uptime Kuma host, not the target. One commenter fixed it by pointing the container straight at a local DNS server, and a maintainer replied that a DNS cache was worth adding. Retries also help, because one slow lookup then won't flip the monitor to down.
Upgrade from Uptime Kuma 1.x
Back up before you change the tag
If you're on louislam/uptime-kuma:1, read the v2.0 release notes before changing the tag. Per those notes, the first start runs a data migration that can take a long time on big databases. Back up ./data first. With SQLite, stop the container so the database file isn't being written while you copy it:
docker compose stop
tar czf kuma-backup-$(date +%F).tgz data
docker compose start
To restore, stop the container and extract the archive in place of ./data. This article doesn't walk through a full 1.x to 2.x migration, and I didn't restore from this backup.
FAQ
Access and setup
How do I reset a forgotten Uptime Kuma password?
Run the reset tool inside the container. The wiki documents the interactive version, npm run reset-password, which asks for the new password twice. With this compose file that's docker compose exec uptime-kuma npm run reset-password. I tested the non-interactive form on 2.5.6, npm run reset-password -- --new-password='...', and it printed "Password reset successfully." The underscore spelling --new_password from older examples failed with "readline was closed". The password can end up in your shell history this way, and the tool warns you about it.
Why does Uptime Kuma show ECONNREFUSED for a service on localhost?
Because localhost inside the container is Uptime Kuma itself, as the reporter in #4517 worked out. Use the Docker gateway IP or host.docker.internal, as shown in the monitor section.
Does Uptime Kuma support PostgreSQL?
Not as of 2.5.6. The setup screen offers Embedded MariaDB, external MariaDB/MySQL and SQLite. PostgreSQL support is still an open feature request (#959).
Alerts and automation
Why do test notifications work but down alerts never arrive?
In my test, the cause was a notification that wasn't attached to the monitor. Edit the monitor and tick the notification, or re-save the notification with Apply on all existing monitors. Also check that the notification service was reachable when the monitor went down. In #6046, a user found that Uptime Kuma doesn't retry a notification if the service is unreachable at that moment, so that alert is simply lost.
Can Uptime Kuma monitor Docker containers?
Yes, with the Docker Container monitor type. The wiki says to mount /var/run/docker.sock into the container, and warns that this gives Uptime Kuma full control over the Docker daemon. It recommends against exposing Uptime Kuma to the internet when you use it. A socket proxy that allows only read access is a common alternative (#4783). I didn't test this monitor type.
Does Uptime Kuma have an API?
Not an official REST API. The feature request (#118) is still open, and in March 2026 a collaborator pointed people to the internal Socket.IO API and third-party wrappers instead. If you build on either, expect changes between releases.
Originally published on DeployLark: Uptime Kuma Docker Setup: Install, Alerts and Common Fixes






Top comments (0)