Back to Blog
Homelab 3 min read

Building a Public Status Page with Uptime Kuma

Public status page showing all systems operational

I wanted a simple way for anyone to check if my services are up — without exposing my internal monitoring. So I set up a public status page at status.pavlohorniak.me, powered by Uptime Kuma, self-hosted on my Raspberry Pi.

Why a Status Page?

I run several public-facing services — my portfolio, a personal API, a fitness dashboard, and a homepage dashboard. They're all hosted on a Raspberry Pi behind a Cloudflare Tunnel. If something goes down, I want to know immediately — and I want others to be able to check without asking me.

Uptime Kuma was already running privately on my Pi, monitoring everything from Pi-hole to Docker containers. The missing piece was making a curated, public-facing view of just the services that matter to visitors.

The Setup

Uptime Kuma

Uptime Kuma is an open-source, self-hosted monitoring tool. It runs as a single Docker container and provides HTTP(s), TCP, DNS, and ping monitoring with a clean UI. I've been running it for months to track all my homelab services.

Prerequisites

If you're starting from a fresh Raspberry Pi (or any Debian-based system), you'll need Docker installed first:

# Update system packages
sudo apt update && sudo apt upgrade -y

# Install Docker via the official convenience script
curl -sSL https://get.docker.com | sh

# Add your user to the docker group (no sudo needed for docker commands)
sudo usermod -aG docker $USER

# Log out and back in, then verify
docker --version

Running with Docker

With Docker ready, deploying Uptime Kuma takes a single command:

docker run -d \
  --name uptime-kuma \
  --restart unless-stopped \
  -p 3002:3001 \
  -v /home/pavlusha/uptime-kuma:/app/data \
  louislam/uptime-kuma:latest

That's it. Port 3001 inside the container is mapped to 3002 on the host (since 3001 was already taken). The data volume persists all monitors, status pages, and notification settings across restarts. With --restart unless-stopped, it comes back automatically after a reboot.

Once running, the dashboard is available at http://192.168.1.216:3002. First launch prompts you to create an admin account — after that, you can start adding monitors for your services.

Uptime Kuma private dashboard showing all monitored services

The key feature here is Status Pages — Uptime Kuma lets you create separate public pages with only the monitors you choose. The private dashboard stays behind authentication while the public page is open to anyone.

Creating the Public Page

In Uptime Kuma, I created a new Status Page with the slug public and added only the four public-facing services:

  • Portfolio — pavlohorniak.me
  • Personal API — api.pavlohorniak.me
  • Fitness Dashboard — fitness.pavlohorniak.me
  • Homepage — home.pavlohorniak.me
Public status page showing four services at 100% uptime

I also added custom CSS to match my portfolio's dark theme — same background colors, accent gold, rounded cards, and font styles. It feels like a natural extension of the site rather than a separate tool.

Nginx Reverse Proxy

Uptime Kuma runs on port 3002, but I didn't want to expose the full application publicly. The login page, settings, and private monitors should stay locked down. So I set up an Nginx config that only proxies the public status page and its required assets:

server {
    listen 80;
    server_name status.pavlohorniak.me;

    location = / {
        return 301 https://status.pavlohorniak.me/status/public;
    }

    location /status/public {
        proxy_pass http://localhost:3002;
        ...
    }

    location /assets { proxy_pass http://localhost:3002; }
    location /upload { proxy_pass http://localhost:3002; }
    location /socket.io { ... }
    location /api/status-page { proxy_pass http://localhost:3002; }

    location / { return 403; }
}

The root URL redirects to the public page. WebSocket support is included for real-time updates. Everything else returns a 403 Forbidden. The private dashboard is only accessible from the local network.

Cloudflare Tunnel

The final step was adding a route in my existing Cloudflare Tunnel to point status.pavlohorniak.me to http://localhost:80 on the Pi. Cloudflare handles SSL termination and caching. No ports opened on the router, no certificates to manage.

Cloudflare Tunnel configuration with status.pavlohorniak.me route

The Result

The status page is now live at status.pavlohorniak.me. It shows real-time uptime percentages, heartbeat history, and an overall status indicator. If any service goes down, the page reflects it immediately.

Live status page at status.pavlohorniak.me showing all systems operational

I also added a link in my portfolio's footer — every page now has a "Status Page" link with a styled tooltip, so visitors can check service health anytime.

Stack Summary

Component Role
Uptime KumaMonitoring & public status page
NginxReverse proxy with selective path exposure
Cloudflare TunnelSecure external access, SSL
Raspberry Pi 5Self-hosted infrastructure
DockerContainer runtime

What's Next

The status page is intentionally minimal — just the essentials. A few things I might add later:

  • Notification integrations (Telegram bot for downtime alerts)
  • Incident history and maintenance windows
  • Response time graphs on the public page

The whole setup took about 30 minutes — most of it was getting the Nginx config right to expose only the public page. If you're already running Uptime Kuma, adding a public status page is a no-brainer.