Instatus is a hosted status page service: it checks your endpoints and shows customers whether your services are up. If you prefer to keep the monitoring data and the incident history on your own infrastructure, Uptime Kuma is the most complete open source replacement: it runs the checks, sends alerts and publishes a public status page from a single container. In this tutorial you will deploy Uptime Kuma with Docker on Ubuntu 24.04, put it behind Nginx with a Let's Encrypt certificate, and publish a status page with incidents and maintenance windows.

Choosing a self-hosted alternative

Several open source projects cover part of what Instatus does. This table summarizes the most common ones:

ProjectRuns the checksPublic status pageNotes
Uptime KumaYes (HTTP, TCP, ping, DNS, push, and more)YesWeb UI for everything, many notification channels
GatusYesYesConfigured with a YAML file, good for GitOps
CachetNoYesStatus page and incident API only, checks come from another tool
cStateNoYesStatic Hugo site, incidents written as Markdown files

Uptime Kuma is the closest match to Instatus for a small team, so it is the one used in this guide. One difference to know up front: Uptime Kuma has no email subscription feature for visitors of the status page. Notifications go to you and your team (email, Slack, Telegram, webhooks and others), and customers check the public page.

Prerequisites

To follow this tutorial you need:

  • A server running Ubuntu 24.04 LTS with at least 1 GB of RAM, for example a CubePath VPS. Run it somewhere other than the services it monitors, otherwise an outage takes down the status page too.
  • A non-root user with sudo privileges.
  • A domain name with an A record for a subdomain such as status.your_domain pointing to your_server_ip.
  • Docker Engine with the Compose plugin. Step 1 installs it if you do not have it yet.

Step 1 - Installing Docker

Uptime Kuma is distributed as a Docker image, which keeps upgrades to a single pull. Install Docker Engine from Docker's official repository:

sudo apt update
sudo apt install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc

Add the repository and install the packages:

echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update
sudo apt install docker-ce docker-ce-cli containerd.io docker-compose-plugin

Check that the daemon is running and Compose is available:

sudo docker compose version
Docker Compose version v2.39.4

The exact version number will differ.

Step 2 - Deploying Uptime Kuma with Docker Compose

Create a directory for the deployment. The data subdirectory will hold the database, so it is the only thing you need to back up:

sudo mkdir -p /opt/uptime-kuma
cd /opt/uptime-kuma

Create the Compose file:

sudo nano /opt/uptime-kuma/compose.yaml
services:
  uptime-kuma:
    image: louislam/uptime-kuma:2
    container_name: uptime-kuma
    restart: unless-stopped
    ports:
      - "127.0.0.1:3001:3001"
    volumes:
      - ./data:/app/data

The port is published only on 127.0.0.1, so Uptime Kuma is not reachable from the internet until Nginx sits in front of it in the next step. The 2 tag follows the 2.x release line, so a pull gets bug fixes without jumping to a new major version.

Start the container:

sudo docker compose up -d

Confirm that it is running and answering locally:

sudo docker compose ps
curl -sI http://127.0.0.1:3001 | head -n 1
NAME          IMAGE                    COMMAND                  SERVICE       CREATED          STATUS                    PORTS
uptime-kuma   louislam/uptime-kuma:2   "/usr/bin/dumb-init …"   uptime-kuma   20 seconds ago   Up 19 seconds (healthy)   127.0.0.1:3001->3001/tcp
HTTP/1.1 302 Found

The 302 is the redirect to the setup page, which you will open once HTTPS is in place.

Step 3 - Publishing Uptime Kuma through Nginx with HTTPS

Install Nginx and Certbot, and open HTTP and HTTPS in the firewall:

sudo apt install nginx certbot python3-certbot-nginx
sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw enable

Create a server block for the status subdomain. Uptime Kuma's interface uses WebSockets, so the Upgrade and Connection headers must be passed through:

sudo nano /etc/nginx/sites-available/uptime-kuma
server {
    listen 80;
    listen [::]:80;
    server_name status.your_domain;

    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-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_read_timeout 3600s;
    }
}

Enable the site, test the syntax and reload Nginx:

sudo ln -s /etc/nginx/sites-available/uptime-kuma /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

Request a certificate. Certbot edits the server block to add the listen 443 ssl directives and an HTTP to HTTPS redirect, and installs a timer that renews the certificate automatically:

sudo certbot --nginx -d status.your_domain

Verify that the site answers over HTTPS:

curl -sI https://status.your_domain | head -n 1
HTTP/2 302

Step 4 - Creating the admin account and the first monitors

Open https://status.your_domain in a browser. On the first visit Uptime Kuma 2 asks which database to use: choose SQLite, which is enough for a few hundred monitors and keeps everything inside /opt/uptime-kuma/data. Then create the administrator account with a strong password.

Add a monitor for each service you want to show on the status page. Click Add New Monitor and fill in:

  • Monitor Type: HTTP(s) for websites and APIs, TCP Port for services such as SMTP or a database port, Ping for hosts, DNS for resolvers.
  • Friendly Name: the name customers will see, for example API.
  • URL: the health endpoint, for example https://api.your_domain/health.
  • Heartbeat Interval: 60 seconds is a sensible default.
  • Retries: 2 or 3, so a single dropped packet does not mark the service as down.

After saving, the monitor page shows a green heartbeat bar within a minute. If it turns red, check the URL from the server itself with curl -I.

Monitoring jobs that cannot be polled

Cron jobs and backups cannot be checked from outside. For those, create a monitor of type Push. Uptime Kuma generates a URL containing a token and marks the monitor as down if it does not receive a request within the heartbeat interval. Call it at the end of the job, only when the job succeeds:

/usr/local/bin/backup.sh && curl -fsS -m 10 --retry 3 "https://status.your_domain/api/push/your_push_token?status=up&msg=OK&ping="

Replace your_push_token with the token shown in the monitor settings, and set the heartbeat interval slightly longer than the job's schedule.

Step 5 - Configuring alert notifications

Notifications tell your team when a monitor changes state. Go to Settings > Notifications > Setup Notification, choose a type (Email (SMTP), Slack, Discord, Telegram, Webhook and many others) and enter its details. For email you need the SMTP host, port, username, password and the sender and recipient addresses from your mail provider.

Click Test before saving; the test message should arrive within a few seconds. Enable Default enabled and Apply on all existing monitors if every monitor should use this channel.

Step 6 - Publishing the status page

Go to Status Pages > New Status Page, enter a name and a slug such as main, and save. In the editor:

  1. Add a description and, optionally, a logo and a footer text.
  2. Create groups (for example Website, API, Email) and add monitors to each group.
  3. Click Save.

The page is now public at https://status.your_domain/status/main. To serve it at the root of the subdomain, open the status page settings, add status.your_domain under Domain Names and save. Visitors to https://status.your_domain will then see the status page, while you keep logging in to the dashboard at https://status.your_domain/dashboard.

Check the public page from a private browser window, where you are not logged in, to see exactly what customers see.

Step 7 - Posting incidents and scheduling maintenance

When something breaks, open the status page, click Edit Status Page and then Create Incident. Give it a title and a message, pick a style (for example warning or danger) and click Post. The incident banner appears at the top of the public page until you click Unpin. Update the message as you learn more, and unpin it once the issue is resolved.

For planned work, use Maintenance in the top menu:

  1. Click Add Maintenance and enter a title and a description shown to visitors.
  2. Choose the strategy (a single window, a recurring schedule or a manual on/off switch) and set the date, time and timezone.
  3. Select the affected monitors and the status pages where the notice should appear.

During the window, affected monitors show a blue maintenance status and do not send down alerts, so a planned reboot does not page anyone.

Step 8 - Backing up and updating Uptime Kuma

All state lives in /opt/uptime-kuma/data. To take a consistent backup, stop the container briefly, archive the directory and start it again:

cd /opt/uptime-kuma
sudo docker compose stop
sudo tar -czf /root/uptime-kuma-$(date +%F).tar.gz -C /opt/uptime-kuma data
sudo docker compose start

Copy the archive to another server or to object storage. To update to the latest 2.x release, take a backup first and then pull the new image:

cd /opt/uptime-kuma
sudo docker compose pull
sudo docker compose up -d

Confirm the new version under Settings > About.

Troubleshooting

The dashboard loads but stays on a spinner or shows "Cannot connect to the socket server". The WebSocket upgrade is not reaching the container. Check that the proxy_http_version 1.1, Upgrade and Connection lines are in the server block Certbot modified, then run sudo nginx -t && sudo systemctl reload nginx. If you use Cloudflare in front of the server, make sure WebSockets are enabled for the zone.

Every monitor shows as down after a restart. Look at the container logs with sudo docker compose logs --tail 50 uptime-kuma. A common cause is that the server cannot resolve DNS or reach the internet; test with curl -I https://example.com from the host.

502 Bad Gateway from Nginx. The container is not running or not listening on 127.0.0.1:3001. Run sudo docker compose ps and start it with sudo docker compose up -d.

Conclusion

You now have a self-hosted status page that checks your services, alerts your team and shows customers the current state, incidents and planned maintenance, all on a server you control. As next steps, add a second Uptime Kuma instance in another location to monitor the first one, schedule the backup from Step 8 with cron, and add Push monitors to your critical cron jobs.