Uptime Kuma with ntfy on Ubuntu 24.04 on Azure User Guide
Overview
This image is uptime monitoring and alert delivery in a single virtual machine. Uptime Kuma runs the checks — HTTP and HTTPS endpoints, TCP ports, DNS records, ping, certificate expiry and more — and shows current state and history on a dashboard. When a check changes state it publishes the alert to ntfy, a push notification server running alongside it on the same instance, where a phone app, a browser tab, a script or a chat bot can pick it up.
The two halves are wired together before you ever sign in, rather than shipped side by side for you to connect. First boot creates the ntfy channel inside Uptime Kuma with this instance's own ntfy access token and marks it the default, so every monitor you create alerts through the local broker with nothing to configure. Because the broker is local, alerts do not depend on an external notification service, an API quota or an outbound webhook, and both the monitoring data and the alert history stay on your own instance.
What is included:
- Uptime Kuma 2.5.0 (MIT) as the monitoring engine and dashboard
- ntfy 2.27.0 (Apache-2.0) as the local publish and subscribe notification server
- Local SQLite storage for both, so the appliance runs standalone with no external database, broker or relay
- A unique administrator password, a unique ntfy user, access token and topic, and a unique TLS certificate, all generated on first boot
- The ntfy channel already registered in Uptime Kuma as the default notification, plus a working self check monitor
- An on instance end to end test that drives a monitor to DOWN and reads the resulting alert back out of ntfy
uptime-kuma.service,ntfy.serviceandnginx.serviceas systemd units, enabled and active- 24/7 cloudimg support
Two things this image deliberately does differently from an unconfigured install:
- Uptime Kuma's first run normally lets whoever reaches the instance first create the administrator account. On a public IP that is a race you can lose to a stranger. First boot creates the administrator itself, so that wizard is already used up before the web interface accepts a connection.
- ntfy's shipped default is
auth-default-access: read-write, meaning anybody may publish to and subscribe to any topic. This image setsdeny-alland creates a single scoped user, so your alert topic is not readable by the internet.
Step 1 — Launch the VM
Deploy the image from the Azure Marketplace, or with the Azure CLI. Standard_B2s is the recommended size and is what this image is tested on.
Open these inbound ports in the network security group:
| Port | Purpose |
|---|---|
| 22 | SSH |
| 443 | Uptime Kuma dashboard (HTTPS) |
| 8443 | ntfy — where your phone, browser or scripts subscribe (HTTPS) |
| 80 | Redirects to 443 only |
Restrict 22 to your own address. If you only ever subscribe from a known network, restrict 8443 to it too.
Step 2 — Confirm the appliance came up
SSH in as azureuser and check the three services. First boot generates every credential on the instance, so give it a few seconds after the VM reports running.
sudo systemctl is-active uptime-kuma.service ntfy.service nginx.service
All three report active.
The first boot unit writes a sentinel and a bootstrap marker when it has finished, and the credentials file it produces is readable only by root:
sudo ls -l /var/lib/cloudimg/
sudo stat -c '%a %U:%G %n' /etc/cloudimg-credentials.txt
There is also a start time guard that the three services run before they are allowed to start. It refuses to let the appliance run on anything other than credentials generated on this instance, so running it by hand is a quick health check:
sudo /usr/local/sbin/uptime-kuma-ntfy-guard.sh
It prints GUARD_OK.

Step 3 — Read your instance's credentials
Every secret on this instance was generated when it first booted. Nothing here shipped inside the image, and no two deployments share a value.
sudo cat /etc/cloudimg-credentials.txt
You get:
| Key | What it is |
|---|---|
KUMA_URL |
The Uptime Kuma dashboard address |
KUMA_ADMIN_USERNAME / KUMA_ADMIN_PASSWORD |
Your Uptime Kuma administrator sign in |
NTFY_URL |
The ntfy server address for subscribers |
NTFY_USERNAME / NTFY_PASSWORD |
Your ntfy sign in, for the app and the web interface |
NTFY_TOKEN |
The access token Uptime Kuma publishes with |
NTFY_TOPIC |
This instance's own topic name, which is not guessable |
NTFY_SUBSCRIBE_URL |
The full URL to point a subscriber at |
TLS_CERTIFICATE |
The certificate generated for this instance |
Store the password somewhere safe and treat this file as a secret.
The pinned upstream versions and the checksums they were verified against are recorded on the image too:
cat /etc/cloudimg-uptime-kuma-ntfy.release

Step 4 — Sign in to the dashboard
Browse to https://<your-vm-ip>/ and sign in with KUMA_ADMIN_USERNAME and KUMA_ADMIN_PASSWORD.
The certificate is generated on your instance and is not signed by a public authority, so your browser shows a warning the first time. That is expected — Step 8 explains how to reissue it for a real DNS name.

Because the administrator account already exists, you are asked to sign in rather than to create an account. Anyone else who reaches the address gets the same sign in prompt.
After signing in you land on the dashboard. The image ships with one monitor already configured — a self check against the local ntfy server — so the dashboard is populated from the start. The screenshot below is from an instance with several monitors added.

Step 5 — Check that the alert channel is already wired up
Go to the profile menu, then Settings, then Notifications. There is already a notification named ntfy (this appliance).

It points at the ntfy server on this instance, publishes to your instance's topic, and authenticates with your instance's access token. It is marked as the default, so it is pre-selected on every new monitor you create. You do not need to change anything.
Step 6 — Create a monitor
Click Add New Monitor, choose a monitor type, give it a name and a target, and save. Leave ntfy (this appliance) ticked under Notifications.
A monitor that cannot reach its target goes red and fires an alert. The detail page shows the check history and the events that produced notifications.

Step 7 — Subscribe to your alerts
This is the half that makes the image a pipeline rather than two programs. Alerts land on your instance's ntfy topic, and anything that can speak HTTP can subscribe.
From a browser or the ntfy mobile app, open the value of NTFY_SUBSCRIBE_URL (that is https://<your-vm-ip>:8443/<your-topic>) and sign in with NTFY_USERNAME and NTFY_PASSWORD. The ntfy app for Android and iOS takes the same server address, topic and credentials.

From a script or the command line, use the access token. This reads back every alert currently cached on your topic and reports how many there are:
sudo bash -c 'TOKEN=$(sed -n "s/^NTFY_TOKEN=//p" /etc/cloudimg-credentials.txt); TOPIC=$(sed -n "s/^NTFY_TOPIC=//p" /etc/cloudimg-credentials.txt); curl -s -H "Authorization: Bearer ${TOKEN}" "http://127.0.0.1:8088/${TOPIC}/json?poll=1" | tee /tmp/alerts.json; echo "alerts currently on topic ${TOPIC}: $(grep -c . /tmp/alerts.json)"; rm -f /tmp/alerts.json'
Each line of output is one alert as JSON, with the monitor name and status in title and the reason in message, followed by a count.
On a brand new instance the count is 0 — nothing has gone down yet, so there is nothing to deliver. Run Step 8 below and then run this again: the count becomes 1 and the alert names the monitor the self test took down. That transition from nothing to something, produced by Uptime Kuma and read back out of ntfy, is the whole product working.
To stream alerts as they happen rather than polling, drop the ?poll=1 and leave the connection open.
Because the server denies access by default, an unauthenticated request to the same topic is refused:
sudo bash -c 'TOPIC=$(sed -n "s/^NTFY_TOPIC=//p" /etc/cloudimg-credentials.txt); curl -s -o /dev/null -w "anonymous subscribe: HTTP %{http_code}\n" "http://127.0.0.1:8088/${TOPIC}/json?poll=1"'
That prints HTTP 403.
Step 8 — Prove the whole pipeline end to end
The image ships with a self test that exercises the real path: it signs in to Uptime Kuma with your administrator credentials, creates a temporary monitor pointing at a closed port so it must fail, waits for it to go DOWN, then subscribes to your ntfy topic and reads the resulting alert back. It removes the temporary monitor afterwards.
sudo /usr/local/sbin/cloudimg-uptime-kuma-e2e-probe.sh
It finishes with E2E_PIPELINE_OK and prints the alert it read back, including the topic, the title and the message.

Run this after any change to the appliance. If it fails, the two halves are not talking and the alerting you rely on is not working.
Step 9 — The listener and authentication model
Neither application process is reachable from the network. Uptime Kuma listens on 127.0.0.1:3001 and ntfy on 127.0.0.1:8088; nginx terminates TLS and is the only thing on a routable address.
sudo ss -ltnp | grep -E '3001|8088|:80 |:443|:8443'

Each application keeps its own authentication as the gate, so nothing is protected by a shared password you cannot rotate:
- Port 443 is the Uptime Kuma dashboard. Uptime Kuma authenticates every session itself with your administrator credentials.
- Port 8443 is ntfy. ntfy denies all access by default and only your instance's user and token are authorised, scoped to your topic.
enable-signup and enable-reservations are off, so the exposed ntfy endpoint cannot be used to register an account or claim a topic. No upstream relay is configured, so no notification leaves the instance unless you choose to configure one.
Step 10 — Reissue the TLS certificate for a real name
The certificate generated at first boot covers loopback, the hostname and the instance's private address, and its public address where Azure exposes one. If you reach the appliance by a public IP or a DNS name, reissue it with that name:
sudo /usr/local/sbin/cloudimg-uptime-kuma-tls-reissue.sh status.example.com
Pass several names at once if you need them. The command replaces the key material, keeps loopback and the private address so the on instance checks keep working, and reloads nginx.
To use a certificate from a public authority instead, place the certificate and key at /etc/ssl/cloudimg/uptime-kuma-ntfy.crt and /etc/ssl/cloudimg/uptime-kuma-ntfy.key, keep the key readable by the www-data group, and run sudo systemctl reload nginx.
Step 11 — Maintenance
Where the data lives. Uptime Kuma's database is /var/lib/uptime-kuma/kuma.db and ntfy's message cache and user database are in /var/lib/ntfy/. Back up both directories.
sudo systemctl stop uptime-kuma.service
sudo tar -czf /root/uptime-kuma-backup.tar.gz /var/lib/uptime-kuma /var/lib/ntfy
sudo systemctl start uptime-kuma.service
Message retention. ntfy caches messages for 12 hours by default. Change cache-duration in /etc/ntfy/server.yml and restart ntfy.service if you want a longer alert history on the server.
Adding more ntfy users. To give a colleague their own credentials rather than sharing yours:
sudo bash -c 'NTFY_CONFIG_FILE=/etc/ntfy/server.yml NTFY_PASSWORD=their-password ntfy user add --role=user colleague'
sudo bash -c 'TOPIC=$(sed -n "s/^NTFY_TOPIC=//p" /etc/cloudimg-credentials.txt); NTFY_CONFIG_FILE=/etc/ntfy/server.yml ntfy access colleague "${TOPIC}" ro'
That grants read only access to your alert topic. Use rw if they also need to publish.
Operating system updates. Unattended upgrades are enabled, so security updates apply automatically. Reboot when a new kernel lands.
Logs.
sudo journalctl -u uptime-kuma.service -n 50 --no-pager
sudo journalctl -u ntfy.service -n 50 --no-pager
If a service will not start, run the guard: it names the exact reason rather than failing silently.
sudo /usr/local/sbin/uptime-kuma-ntfy-guard.sh
Support
Every cloudimg image comes with 24/7 support. Contact us at support@cloudimg.co.uk.