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:
| Project | Runs the checks | Public status page | Notes |
|---|---|---|---|
| Uptime Kuma | Yes (HTTP, TCP, ping, DNS, push, and more) | Yes | Web UI for everything, many notification channels |
| Gatus | Yes | Yes | Configured with a YAML file, good for GitOps |
| Cachet | No | Yes | Status page and incident API only, checks come from another tool |
| cState | No | Yes | Static 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
sudoprivileges. - A domain name with an
Arecord for a subdomain such asstatus.your_domainpointing toyour_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 Portfor services such as SMTP or a database port,Pingfor hosts,DNSfor resolvers. - Friendly Name: the name customers will see, for example
API. - URL: the health endpoint, for example
https://api.your_domain/health. - Heartbeat Interval:
60seconds is a sensible default. - Retries:
2or3, 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:
- Add a description and, optionally, a logo and a footer text.
- Create groups (for example
Website,API,Email) and add monitors to each group. - 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:
- Click Add Maintenance and enter a title and a description shown to visitors.
- Choose the strategy (a single window, a recurring schedule or a manual on/off switch) and set the date, time and timezone.
- 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.
