Observability Azure

Uptime Kuma with ntfy on Ubuntu 24.04 on Azure User Guide

| Product: Uptime Kuma with ntfy on Ubuntu 24.04 LTS on Azure

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.service and nginx.service as 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 sets deny-all and 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.

The three services reporting active, the first boot marker and sentinel present, the credentials file owned by root with mode 0600, and the start guard reporting 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

The recorded upstream versions with the pinned Uptime Kuma commit, the dist archive and ntfy package SHA256 checksums, and the licence of each component


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.

The Uptime Kuma sign in page served over HTTPS by the instance's own certificate

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.

The Uptime Kuma dashboard showing quick stats of four monitors up and one down, a list of monitors with uptime percentages and heartbeat bars, and a recent events table naming which monitor went down and why


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).

The Uptime Kuma notification settings page listing the ntfy channel that first boot registered for this instance

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.

The detail page for a monitor that is down, showing its response history and the events that fired 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.

The ntfy web interface subscribed to this instance's topic, showing two delivered alerts naming the monitors that went down and the reason for each

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.

The end to end self test signing in, creating a failing monitor, and reading the resulting DOWN alert back out of the local ntfy server before printing E2E_PIPELINE_OK

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'

The listening sockets, with Uptime Kuma on port 3001 and ntfy on port 8088 both bound to the loopback address, and only nginx on ports 80, 443 and 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.