On this page
A monitor on the same server as your app can disappear with the problem. Put Uptime Kuma elsewhere, then test the alert with a controlled failure.
Decide what this monitor can prove
Run Uptime Kuma on a different server from the site it checks. An application-server failure should not stop the monitor. Two Droplets at one provider still do not cover a provider-wide outage.
Before creation, budget for a separate paid server and backups. On 8 October 2026 the bundled Basic prices list US$6/month for 1 GiB and US$12/month for 2 GiB; neither is a tested capacity guarantee for your monitor count. Basic backups add 20% weekly or 30% daily, so a US$6 example totals US$7.20 or US$7.80 before tax, domain, extra storage or transfer. Confirm your region and configuration before creation; v5 hourly configurations use a different billing model.
Bootstrap safety hold: the pinned companion exposes Caddy publicly when the whole stack starts. HTTPS and a closed direct port 3001 do not restrict who can reach first-account setup through Caddy. This guide does not yet provide a tested private-bootstrap/ACME sequence. Do not provision or start it as a turnkey public setup; first prepare and validate the owner-only bootstrap described below on an authorized disposable instance.
Create and restrict the monitor server
Create a fresh Ubuntu 24.04 LTS Droplet. A region near the site reduces latency; a different region checks another network path. Choose the path you want to monitor.
- In DigitalOcean, create a Droplet with an SSH key. Name it for its role, such as uptime-kuma-monitor, rather than after the site it checks.
- Create a Cloud Firewall and attach it only to this Droplet. Keep inbound TCP 80 and 443 closed during first-account bootstrap. Open the required public certificate-challenge/dashboard access only after the private bootstrap gate below passes. Allow TCP 22 only from your current administrator address or VPN range.
- Create an A or AAAA record for the dashboard name that points to the monitor Droplet. Wait for it to resolve before starting Caddy, because Caddy uses the name to obtain a public certificate.
- Do not open Uptime Kuma's own port 3001 in the firewall. The companion Compose file keeps it on the internal Docker network and lets Caddy be the only public listener.
ssh root@YOUR_DROPLET_IP
apt-get update
apt-get install -y docker.io docker-compose-v2 git nano python3
systemctl enable --now docker
docker compose version
cd /root
Deploy a Next.js app with pull request previews
Start Uptime Kuma behind HTTPS
Run the following commands in the root SSH terminal opened above. In Nano, save with Ctrl+O and Enter, then exit with Ctrl+X before continuing.
The companion repository keeps the configuration short on purpose. Uptime Kuma stores its state under ./data, while Caddy owns ports 80 and 443 and proxies to the Uptime Kuma container. The setup uses Uptime Kuma's maintained major-version image tag. Review the project's update notes before changing it.
Uptime Kuma documents that its data must live on a local directory or volume, not NFS. Keep the data on the monitor Droplet's local disk and include it in your server backup plan. The monitor should be boring enough that you can rebuild it, but its notification settings and monitor history are still useful data.
If the dashboard check fails, first confirm DNS resolves to this Droplet and that the Cloud Firewall is attached. Then inspect the relevant service status. Do not open port 3001 as a shortcut: this configuration intentionally keeps it private behind Caddy, so exposing it would change the security boundary rather than prove the installation.
The companion is pinned to 941abf472a35 and targets Uptime Kuma major version 2. Its louislam/uptime-kuma:2 and caddy:2 image tags still move. The source review does not establish a tested image digest. Before relying on a rehearsal, record the exact image IDs/digests and freeze those in your deployment; repeat the acceptance checks when changing them. Upstream installation guidance requires local data storage and excludes NFS. Preserve ./data and read the update procedure before pulling and recreating containers.
git clone https://github.com/TylorMayfield/uptime-kuma-droplet-monitor.git
git -C uptime-kuma-droplet-monitor checkout --detach 941abf472a350a6c934d7590fd473dc45ea203ea
test "$(git -C uptime-kuma-droplet-monitor rev-parse HEAD)" = 941abf472a350a6c934d7590fd473dc45ea203ea
cd uptime-kuma-droplet-monitor
cp .env.example .env
nano .env # Set KUMA_DOMAIN to the DNS name you created.
chmod 700 scripts/preflight.sh scripts/check-dashboard.sh
./scripts/preflight.sh
Required private-bootstrap acceptance gate
Stop after configuration above. The unchanged companion does not supply this gate, and its preflight is not proof of private access. Keep public Caddy stopped and ports 80/443/3001 unreachable from the internet until the first owner is established. Have an operator prepare a local-only Kuma listener with an SSH tunnel, or an equivalent administrator-only route, and test the resolved Compose bindings with the exact recorded image digests. No untested override/start commands are supplied here.
On fresh disposable data, verify that the intended private route works and an outside client cannot reach setup. Create the first administrator only through that private route, then verify the intended account's authenticated login and a restart. If setup is already claimed unexpectedly, stop and investigate or rebuild from trusted state; do not trust an unknown account.
Only after those checks, validate the transition to the intended dashboard access and Caddy certificate issuance. Caddy's public HTTP/TLS challenges need their documented network reachability; an allowlist that blocks challenges is not a complete TLS recipe. Record the bootstrap, exposure, ACME and subsequent unauthenticated-setup rejection checks before proceeding. Keep the private gate in place until that transition has been rehearsed. Choosing a public status page remains a separate decision.
Create one monitor and one notification before adding more
Continue only after the private-bootstrap gate passes. Sign in as the administrator already created through the private route at https://KUMA_DOMAIN. Treat it as a private operations dashboard. Do not make it a public status page by accident. A public status page is a separate choice for people who need to check your service, and it can expose service names and outage history.
In the dashboard, add a notification method you can test. Then add an HTTP(s) monitor for a harmless health URL, set its expected status code, and attach the notification. Start with one monitor. Add more monitors after the down and recovery test succeeds.
- Send Uptime Kuma's notification test and confirm it reaches the intended person or channel.
- Create an HTTP(s) monitor for a health URL that returns a predictable success response without changing application state.
- Set a timeout and retry behavior that match the service. Do not choose a tiny timeout just to make the dashboard look busy.
- Give the monitor a name that says what failed, such as Website HTTPS health check, rather than a vague name such as Production.
Prove an alert and recovery without taking down production
After the private-bootstrap and TLS checks pass, create a temporary HTTP monitor in Uptime Kuma for http://host.docker.internal:8099, expecting status 200, with your notification enabled. For this test add extra_hosts: ["host.docker.internal:host-gateway"] to the Uptime Kuma service in your Compose file and recreate it. The small server below listens on the host; keep 8099 closed to the internet in the Cloud Firewall. It initially returns 503.
Save the Python below as /root/kuma-test-target.py. In a second SSH terminal, run python3 /root/kuma-test-target.py and keep it open. Wait for the down notification. In the first terminal, create /tmp/kuma-test-healthy with touch /tmp/kuma-test-healthy. Wait for recovery, then delete the temporary monitor, stop Python with Ctrl+C, and remove the marker file. Do not change the production site.
from http.server import BaseHTTPRequestHandler, HTTPServer
from pathlib import Path
marker = Path("/tmp/kuma-test-healthy")
marker.unlink(missing_ok=True)
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
status = 200 if marker.exists() else 503
self.send_response(status)
self.end_headers()
self.wfile.write(str(status).encode())
HTTPServer(("0.0.0.0", 8099), Handler).serve_forever()
cd /root/uptime-kuma-droplet-monitor
docker compose up -d
./scripts/check-dashboard.sh
Keep the signal useful
Review monitors after the first false alert. If a transient failure is common and harmless, change the retry or timeout after you understand it. Do not silence a monitor because it caught an inconvenient problem. Route alerts to people who can act on them, and remove destinations when responsibilities change.
Update Uptime Kuma deliberately. Back up its local data, read the upstream update notes, pull the image, recreate the services, and recheck one dashboard login, one monitor, and one notification.
A restore rehearsal must recover one monitor definition and its notification settings from the stopped service's off-host data copy. Start the copy in an isolated environment with outbound notifications blocked, inspect the recovered configuration, then use a separate disposable notification target for a controlled down/recovery test. Record the source commit, image digests, backup timestamp and results. Do not report the restored monitor as proven merely because the dashboard responds.
Frequently asked questions
Should Uptime Kuma run on the same server as my app?
No. Put it on a separate server so an application-server failure does not also remove the monitor. This still does not prove availability outside the provider or region you choose.
Do I need to make the Uptime Kuma dashboard public?
No. Initialize the administrator through a verified private route first and choose the dashboard’s later access separately. The private-bootstrap sequence remains a required rehearsal gate in this guide. A public status page is optional and should contain only the information you intend to share.
How do I test an Uptime Kuma alert safely?
Use a disposable monitor and a temporary target that returns an error. Confirm both the down and recovery notifications. Do not take down a production site just to test the notification path.
What is the minimum evidence that monitoring works?
A private dashboard over HTTPS is only the start. Send a notification test, then use a temporary controlled failure and confirm both the down and recovery notifications arrive. Record their delay before relying on a production monitor.