Project | Monitoring

Live Service Status

Real-time uptime monitoring for public-facing services - powered by a self-hosted Uptime Kuma instance running inside a Proxmox LXC container. The data below is live and intentionally high-level.

Uptime Kuma Proxmox LXC Self-Hosted Cloudflare Tunnel NPMplus SQLite

Loading live status…

What I Built

  • Deployed Uptime Kuma 2.x on Node.js 20 inside a Proxmox LXC, with SQLite as the backend — no external database dependencies.
  • Wrote Node.js scripts using the Uptime Kuma socket.io API to bulk-provision all monitors programmatically rather than clicking through the UI.
  • Created a curated public status page exposing only sanitized, generic service names — no internal IPs, hostnames, or network topology leaked.
  • Proxied the status page through NPMplus reverse proxy with a valid wildcard TLS certificate, then published it externally via Cloudflare Tunnel in the DMZ VLAN.
  • Monitors cover HTTP services, ping targets, and public DNS endpoints — 30+ services across the homelab checked every 60 seconds.

Skills Demonstrated

  • Self-hosted monitoring stack deployment and configuration
  • Proxmox LXC provisioning and Node.js runtime setup
  • Socket.io API automation for bulk service provisioning
  • SQLite schema navigation and direct database management
  • Reverse proxy configuration with TLS termination
  • Secure public exposure via Cloudflare Tunnel while management ports stay off WAN
  • Operational security — sanitizing public-facing data to avoid info leakage
Deployment

LXC on Proxmox

Uptime Kuma runs in a lightweight Debian LXC container on the Proxmox cluster — no full VM overhead. Node.js 20 is installed directly, the app runs as a systemd service with auto-restart, and SQLite keeps all data local with no external database to maintain. The whole stack uses under 150 MB of RAM at idle.

Automation

API-Driven Provisioning

All 30+ monitors were added via the Uptime Kuma socket.io WebSocket API using a custom Node.js script — not manual UI clicks. This meant discovering the exact payload schema the API expects (including required fields like conditions and accepted_statuscodes), handling sequencing to avoid race conditions, and debugging directly against the SQLite database when the API returned ambiguous errors.

Security

Public Without Exposure

The status page is publicly reachable at status.masternazz.com but reveals nothing about the internal network. Service names are generic ("Media Streaming", "Authentication"), no internal IPs or hostnames appear, and this status path is served through Cloudflare Tunnel. Other operational services may use documented WAN forwards, but the monitoring backend itself is only accessible from inside the LAN.

Monitoring Stack

How each layer fits together from check to public display.

Layer Component Role
MonitorUptime Kuma 2.xHTTP & ping checks every 60s, SQLite storage
Reverse ProxyNPMplusTLS termination, routes status.masternazz.com → Kuma LXC
Public IngressCloudflare TunnelPublishes the status path without exposing management ports
DNSAdGuard Home + CloudflareInternal rewrites for LAN, public DNS via Cloudflare
ComputeProxmox LXCLightweight container hosting the Node.js app

Employer-Relevant Skills

Running your own monitoring stack end-to-end — from provisioning the container to writing the API automation to securing the public endpoint — covers a broad slice of operations work. The useful lessons came from debugging the details: why the socket API dropped payloads, why a JSON column in SQLite broke the login handler, and why a status code range needed to be a string array rather than an integer range.

Explore More Projects

Infrastructure this monitoring stack sits on top of.