Monitoring beginner ⏱ 30 minutes

Uptime Kuma: Self-Hosted Monitoring Guide

Professional uptime monitoring with 95+ notification channels. Track websites, APIs, containers, and more.

By homelabbing.io • 2025-12-16
#uptime-kuma#monitoring#alerts#self-hosted#docker

Introduction

In the world of self-hosted infrastructure, there’s a fundamental problem every homelab administrator faces: when your services go down, how do you know? More importantly, if your entire homelab goes offline, how can you be alerted when your monitoring system is down too?

Uptime Kuma solves these challenges elegantly. It’s an open-source, self-hosted monitoring solution that tracks the availability and performance of your websites, APIs, containers, and network services. With 76,000+ GitHub stars, 127+ million Docker pulls, and support for 95+ notification channels, Uptime Kuma has become the de facto standard for homelab monitoring.

What you’ll learn:

  • Deploy Uptime Kuma with Docker Compose in under 15 minutes
  • Create monitors for HTTP, TCP, Docker containers, DNS, and more
  • Configure multi-channel notifications (Discord, Telegram, Email, Slack)
  • Implement AI-powered alert analysis and automation
  • Set up external monitoring to survive ISP outages
  • Optimize performance and reduce alert fatigue

Prerequisites:

  • Docker and Docker Compose installed (v20.10+ and v1.29+)
  • Basic understanding of Docker containers
  • 500MB+ free disk space (5-10GB recommended)
  • A notification service account (Discord, Telegram, or email)

Why Uptime Kuma?

The Homelab Monitoring Challenge

Traditional monitoring solutions present several challenges:

  1. Cloud-based services (Uptime Robot, Pingdom) store your infrastructure data externally
  2. Enterprise tools (Nagios, Zabbix) have steep learning curves and complex configurations
  3. Limited customization in many SaaS offerings
  4. Recurring costs for adequate monitoring ($8-50+ monthly)

Uptime Kuma addresses all these concerns:

  • Zero cloud dependency: Everything stays on your infrastructure
  • Beginner-friendly: Web-based UI, no configuration files required
  • Powerful features: Multiple monitor types, extensive notifications, public status pages
  • Free and open-source: No licensing costs, active community development
  • Lightweight: Runs on Raspberry Pi 4 with 12+ monitors

Key Features at a Glance

FeatureDetails
Monitor TypesHTTP/HTTPS, TCP Port, Ping, DNS, Docker, Push (Dead Man’s Switch), WebSocket
Check Interval20 seconds minimum, configurable up to hours
Notifications95+ channels: Discord, Slack, Telegram, Email, Teams, PagerDuty, Twilio, SMS, and more
Status PagesPublic status pages with customizable branding and badges
AuthenticationAdmin account with 2FA support, API keys for automation
DatabaseSQLite (default) or PostgreSQL for enterprise scale
DeploymentDocker, Docker Compose, or native installation

Understanding Monitoring Fundamentals

Poll-Based vs. Push-Based Monitoring

Uptime Kuma supports two monitoring paradigms:

Poll-Based (Active) Monitoring:

  • Uptime Kuma actively checks your services at regular intervals
  • Best for: Websites, APIs, databases, always-available services
  • Example: HTTP check every 60 seconds to verify website responds with status 200

Push-Based (Passive) Monitoring:

  • Your services report their status to Uptime Kuma (Dead Man’s Switch)
  • Uptime Kuma alerts if reports stop arriving
  • Best for: Cron jobs, backup tasks, scheduled scripts
  • Example: Backup script sends “success” ping after completion; alert if no ping within 24 hours

Monitor Types Explained

Monitor TypeUse CaseCheck TimeExample Scenario
HTTP(s)Websites, REST APIs< 1 secVerify homepage returns 200 OK
TCP PortDatabase servers, SSH< 1 secCheck MySQL port 3306 is accepting connections
PingServer availability< 1 secVerify router is responding to ICMP
DNS RecordName resolution< 2 secConfirm Pi-hole resolves example.com correctly
DockerContainer health~5 secMonitor PostgreSQL container is running
PushScheduled tasksN/ABackup job completion notification
WebSocketReal-time services< 1 secChat server WebSocket connection

Installation: Docker Compose Method

Step 1: Create Project Directory

mkdir -p ~/uptime-kuma
cd ~/uptime-kuma

Step 2: Create docker-compose.yml

This configuration provides a production-ready Uptime Kuma deployment:

version: '3.8'

services:
  uptime-kuma:
    image: louislam/uptime-kuma:1
    container_name: uptime-kuma
    volumes:
      - ./data:/app/data
      # Uncomment to enable Docker container monitoring:
      # - /var/run/docker.sock:/var/run/docker.sock
    ports:
      - "3001:3001"
    restart: always
    environment:
      - TZ=America/New_York  # Change to your timezone
      # Optional: Enable iframe embedding for status pages
      # - UPTIME_KUMA_DISABLE_FRAME_SAMEORIGIN=true
    networks:
      - monitoring

networks:
  monitoring:
    driver: bridge

Configuration Notes:

  • image: louislam/uptime-kuma:1 - Stable version 1.x branch (use :2 for latest v2.x)
  • ./data:/app/data - Persistent storage for database and configuration
  • TZ environment variable ensures timestamps match your local time
  • Docker socket binding enables container monitoring (commented out by default for security)

Step 3: Start the Container

docker compose up -d

Step 4: Verify Installation

# Check container is running
docker ps | grep uptime-kuma

# View startup logs
docker logs uptime-kuma

# Check resource usage
docker stats uptime-kuma --no-stream

Expected output:

CONTAINER ID   NAME           CPU %   MEM USAGE / LIMIT   MEM %
abc123def456   uptime-kuma    0.5%    120MiB / 2GiB      6%

Step 5: Access Web Interface

Navigate to: http://YOUR_SERVER_IP:3001

Initial setup wizard:

  1. Create admin username and password (save in password manager)
  2. Select preferred language
  3. Configure timezone (matches TZ environment variable)
  4. Complete setup

Creating Your First Monitors

HTTP/HTTPS Website Monitoring

The most common monitoring type for web services.

Scenario: Monitor your personal website or blog

  1. Click “Add New Monitor”

  2. Configure basic settings:

    • Monitor Type: HTTP(s)
    • Friendly Name: “My Website”
    • URL: https://example.com
    • Heartbeat Interval: 60 seconds
    • Timeout: 10 seconds
    • Max Retries: 2
  3. Advanced options (expand section):

    • Expected Status Code: 200 (or 301 for redirects)
    • Expected Keyword: Enter text that should appear on page (e.g., “Welcome”)
    • HTTP Headers: Add custom headers if needed
      Authorization: Bearer YOUR_TOKEN
      User-Agent: Uptime-Kuma/1.0
  4. Click “Save”

What happens: Uptime Kuma sends HTTP GET request every 60 seconds, verifies response is 200 OK, optionally checks for keyword, and measures response time.

TCP Port Monitoring

Monitor any service listening on a TCP port.

Scenario: Monitor PostgreSQL database availability

  1. Click “Add New Monitor”

  2. Configure:

    • Monitor Type: TCP Port
    • Friendly Name: “PostgreSQL Database”
    • Hostname: 192.168.1.100 (or database.local)
    • Port: 5432
    • Heartbeat Interval: 300 seconds (5 minutes)
  3. Click “Save”

Common TCP port monitors:

  • MySQL: port 3306
  • PostgreSQL: port 5432
  • Redis: port 6379
  • SSH: port 22
  • SMTP: port 25 or 587

Docker Container Monitoring

Monitor Docker container health directly.

Prerequisites: Enable Docker socket access

Modified docker-compose.yml:

services:
  uptime-kuma:
    image: louislam/uptime-kuma:1
    container_name: uptime-kuma
    volumes:
      - ./data:/app/data
      - /var/run/docker.sock:/var/run/docker.sock  # Add this line
    ports:
      - "3001:3001"
    restart: always

Apply changes:

docker compose down
docker compose up -d

Create Docker monitor:

  1. Click “Add New Monitor”

  2. Configure:

    • Monitor Type: Docker Container
    • Friendly Name: “PostgreSQL Container”
    • Docker Container: Select from dropdown (e.g., postgres)
    • Heartbeat Interval: 60 seconds
  3. Click “Save”

How it works: Uptime Kuma queries Docker API to check if container state is “running”. If container stops, restarts, or becomes unhealthy, monitor triggers alert.

DNS Record Monitoring

Verify DNS resolution is working correctly.

Scenario: Monitor Pi-hole DNS server

  1. Click “Add New Monitor”

  2. Configure:

    • Monitor Type: DNS
    • Friendly Name: “Pi-hole DNS Server”
    • Hostname: example.com
    • DNS Server: 192.168.1.5 (your Pi-hole IP)
    • Record Type: A
    • Expected Value: 93.184.216.34 (example.com’s IP)
    • Heartbeat Interval: 300 seconds
  3. Click “Save”

Use cases:

  • Verify internal DNS server (Pi-hole, AdGuard Home) is resolving correctly
  • Monitor DNS propagation after domain changes
  • Detect DNS hijacking or misconfiguration

Configuring Notifications

Monitoring without notifications is like having a smoke detector with no alarm. Let’s configure multi-channel alerting.

Discord webhooks are the easiest notification method for homelabbers.

Create Discord webhook:

  1. Open Discord server (or create new one)
  2. Right-click server → Create Channel → Name: #uptime-alerts
  3. Click channel name → Integrations → Webhooks
  4. Click “New Webhook”
  5. Name: “Uptime Kuma”
  6. Copy webhook URL (looks like: https://discord.com/api/webhooks/123456789/abcdef...)

Configure in Uptime Kuma:

  1. Navigate to Settings → Notifications

  2. Click “Add Notification”

  3. Configure:

    • Notification Type: Discord
    • Friendly Name: “Discord Alerts”
    • Discord Webhook URL: Paste URL from step 6 above
    • Bot Display Name: “Uptime Monitor” (optional)
  4. Click “Test” to verify

  5. Check Discord channel for test message

  6. Click “Save”

Example Discord notification:

🔴 DOWN - API Gateway
Status changed from UP to DOWN
Duration: 0 seconds
Time: 2025-12-16 14:23:45

Response: Connection refused

Telegram Bot Setup

Mobile-first notifications with group chat support.

Create Telegram bot:

  1. Open Telegram app
  2. Search for @BotFather
  3. Send /start command
  4. Send /newbot command
  5. Follow prompts:
    • Bot name: “Homelab Monitor”
    • Bot username: “homelab_monitor_bot” (must be unique)
  6. Save API token: 123456789:ABCdefGHIjklMNOpqrsTUVwxyz

Get Chat ID:

  1. Create Telegram group for alerts (or use direct message)
  2. Add your bot to the group
  3. Send any message in the group
  4. Visit: https://api.telegram.org/bot123456789:ABCdefGHIjklMNOpqrsTUVwxyz/getUpdates (Replace with your bot token)
  5. Find JSON response, locate: "chat":{"id":-987654321}
  6. Save Chat ID: -987654321

Configure in Uptime Kuma:

  1. Settings → Notifications → Add Notification

  2. Configure:

    • Type: Telegram
    • Friendly Name: “Telegram Alerts”
    • Bot Token: Paste token from BotFather
    • Chat ID: Paste chat ID from getUpdates
  3. Click “Test” → Check Telegram for message

  4. Click “Save”

Email Notifications (SMTP)

Universal notification method, works with any email provider.

Gmail configuration:

  1. Generate App Password (required for 2FA accounts):

  2. Configure in Uptime Kuma:

    • Settings → Notifications → Add Notification
    • Type: Email (SMTP)
    • Friendly Name: “Email Alerts”
    • SMTP Host: smtp.gmail.com
    • SMTP Port: 587
    • Security: STARTTLS
    • Username: [email protected]
    • Password: Paste app password (remove spaces)
    • From Email: [email protected] (or your Gmail)
    • To Email: [email protected]
  3. Click “Test” → Check email inbox

  4. Click “Save”

Alternative email providers:

  • Mailgun: Free tier, SMTP at smtp.mailgun.org:587
  • SendGrid: Free 100 emails/day, SMTP at smtp.sendgrid.net:587
  • Brevo (Sendinblue): Generous free tier

Assigning Notifications to Monitors

Once notifications are configured, assign them to monitors:

  1. Open existing monitor or create new one

  2. Scroll to “Notifications” section

  3. Check boxes for which notification channels to use

  4. Recommended strategy:

    • Critical services: All channels (Discord + Telegram + Email)
    • Important services: 2 channels (Discord + Telegram)
    • Low-priority services: 1 channel (Discord only)
  5. Click “Save”

Pro tip: Create notification groups for different service tiers to avoid alert fatigue.


Creating Public Status Pages

Share your infrastructure status with team members or customers.

Basic Status Page Setup

  1. Navigate to Status Pages section

  2. Click “Add Status Page”

  3. Configure:

    • Title: “Infrastructure Status”
    • Description: “Real-time availability of critical services”
    • Slug: status (becomes URL: /status/status)
  4. Add monitors to page:

    • Click “Edit” on status page
    • Create groups: “Network”, “Storage”, “Applications”
    • Drag monitors into appropriate groups
    • Reorder as needed
  5. Click “Publish”

  6. Copy public URL to share

Advanced Customization

Theme options:

  • Primary Color: Match your brand
  • Logo Upload: Custom logo in header
  • Custom CSS: Advanced styling

Example custom CSS:

/* Custom status page styling */
.status-page-header {
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    padding: 2rem;
}

.uptime-badge {
    font-weight: bold;
    border-radius: 8px;
}

Embedding Status Badges

Generate embeddable badges for GitHub README or external websites.

Badge types:

  • Status badge: Current UP/DOWN status
  • Ping badge: Average response time
  • Uptime badge: Uptime percentage for time period
  • Certificate badge: SSL certificate expiration

Badge URLs:

Status: https://uptime.example.com/api/badge/1/status
Ping (24h): https://uptime.example.com/api/badge/1/ping/24
Uptime (30d): https://uptime.example.com/api/badge/1/uptime/720

Markdown embedding:

# My Project

![Website Status](https://uptime.example.com/api/badge/1/status)
![Uptime 30d](https://uptime.example.com/api/badge/1/uptime/720)

Security Hardening

Enable Two-Factor Authentication (2FA)

Protect your monitoring dashboard with 2FA.

Setup steps:

  1. Settings → Advanced → Two-Factor Authentication

  2. Click “Enable 2FA”

  3. Scan QR code with authenticator app:

    • Google Authenticator
    • Authy
    • Microsoft Authenticator
    • 1Password (has built-in TOTP)
  4. Enter 6-digit code to confirm

  5. Save backup codes in password manager (single-use recovery codes)

  6. Logout and login to verify 2FA is working

Important: If you lose 2FA access, you’ll need backup codes to regain access.

Reverse Proxy with HTTPS

Expose Uptime Kuma securely with SSL/TLS encryption.

Nginx configuration:

# /etc/nginx/sites-available/uptime-kuma
server {
    server_name uptime.example.com;

    location / {
        proxy_pass http://localhost:3001;
        proxy_http_version 1.1;

        # WebSocket support (CRITICAL for real-time updates)
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";

        # Standard proxy headers
        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;
    }

    listen 443 ssl http2;
    ssl_certificate /etc/letsencrypt/live/uptime.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/uptime.example.com/privkey.pem;
}

# Redirect HTTP to HTTPS
server {
    listen 80;
    server_name uptime.example.com;
    return 301 https://$server_name$request_uri;
}

Enable configuration:

sudo ln -s /etc/nginx/sites-available/uptime-kuma /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

Caddy configuration (simpler alternative):

# /etc/caddy/Caddyfile
uptime.example.com {
    reverse_proxy localhost:3001 {
        header_up X-Forwarded-Proto https
    }
}

Enable configuration:

sudo systemctl reload caddy

After configuring reverse proxy:

  1. In Uptime Kuma: Settings → Reverse Proxy
  2. Enable “Trust Proxy Headers”
  3. Save

AI Automation & Intelligent Monitoring

Enhance Uptime Kuma with AI-powered automation for smarter alerts and self-healing infrastructure.

1. Intelligent Alert Aggregation

Problem: Multiple services fail simultaneously, creating alert flood.

Solution: Use AI to identify root cause and suppress secondary alerts.

Implementation:

# alert_analyzer.py
from anthropic import Anthropic
import json

client = Anthropic(api_key="your-api-key")

def analyze_alert_cascade(alerts: list) -> dict:
    """
    Analyze multiple alerts to identify root cause using Claude.

    Args:
        alerts: List of alert dictionaries with service, status, timestamp

    Returns:
        Analysis with root cause and recommended actions
    """
    alert_summary = "\n".join([
        f"- {alert['service']}: {alert['status']} at {alert['timestamp']}"
        for alert in alerts
    ])

    response = client.messages.create(
        model="claude-opus-4-5-20251101",
        max_tokens=500,
        messages=[{
            "role": "user",
            "content": f"""
            Analyze these infrastructure alerts and identify the root cause:

            {alert_summary}

            Service dependencies:
            - Website depends on API Gateway and CDN
            - API Gateway depends on Database and Cache
            - Cache depends on Database
            - All services depend on Network

            Provide:
            1. Most likely root cause
            2. Which alerts are secondary/cascading failures
            3. Recommended first action to resolve

            Format as JSON.
            """
        }]
    )

    return json.loads(response.content[0].text)

# Example usage
alerts = [
    {"service": "Website", "status": "DOWN", "timestamp": "2025-12-16 10:23:45"},
    {"service": "API Gateway", "status": "DOWN", "timestamp": "2025-12-16 10:23:47"},
    {"service": "Database", "status": "DOWN", "timestamp": "2025-12-16 10:23:42"}
]

analysis = analyze_alert_cascade(alerts)
print(f"Root Cause: {analysis['root_cause']}")
print(f"Action: {analysis['recommended_action']}")

Example output:

{
  "root_cause": "Database server failure",
  "secondary_alerts": ["API Gateway", "Website"],
  "recommended_action": "Investigate Database container logs and restart if needed",
  "estimated_impact": "All user-facing services unavailable"
}

2. Predictive Failure Detection

Concept: Analyze response time trends to predict failures before they occur.

Implementation:

# predictive_monitoring.py
import numpy as np
from sklearn.linear_model import LinearRegression
from uptime_kuma_api import UptimeKumaApi

api = UptimeKumaApi('http://localhost:3001')
api.login('admin', 'your-password')

def predict_service_failure(monitor_id: int, threshold_ms: int = 10000) -> dict:
    """
    Predict when service will exceed timeout threshold based on trends.

    Args:
        monitor_id: Uptime Kuma monitor ID
        threshold_ms: Timeout threshold in milliseconds

    Returns:
        Prediction with days until failure and confidence
    """
    # Get last 100 heartbeats
    heartbeats = api.get_monitor_heartbeats(monitor_id=monitor_id, limit=100)
    response_times = [hb['ping'] for hb in heartbeats if hb['ping']]

    if len(response_times) &lt; 20:
        return {"status": "insufficient_data"}

    # Fit linear regression to trend
    X = np.array(range(len(response_times))).reshape(-1, 1)
    y = np.array(response_times)

    model = LinearRegression()
    model.fit(X, y)

    # Calculate when response time will exceed threshold
    current_time = response_times[-1]
    trend_per_check = model.coef_[0]

    if trend_per_check <= 0:
        return {"status": "improving", "trend": "Response times decreasing"}

    # Calculate days until failure (assuming 1 check per minute)
    checks_until_failure = (threshold_ms - current_time) / trend_per_check
    days_until_failure = checks_until_failure / 1440  # 1440 minutes per day

    if days_until_failure &lt; 0:
        return {
            "status": "CRITICAL",
            "prediction": "Service already exceeding threshold",
            "current_response_time": current_time,
            "threshold": threshold_ms
        }
    elif days_until_failure &lt; 7:
        return {
            "status": "WARNING",
            "prediction": f"Service will timeout in {days_until_failure:.1f} days",
            "current_response_time": current_time,
            "trend_per_day": trend_per_check * 1440,
            "recommended_action": "Investigate performance degradation"
        }
    else:
        return {
            "status": "OK",
            "prediction": f"Service healthy, {days_until_failure:.0f} days headroom"
        }

# Example usage
prediction = predict_service_failure(monitor_id=1)
print(f"Status: {prediction['status']}")
print(f"Prediction: {prediction.get('prediction', 'No prediction available')}")

api.disconnect()

3. Automated Self-Healing

Concept: Automatically restart failing containers via webhook.

Implementation:

# auto_healing_service.py
from flask import Flask, request, jsonify
import docker
import logging

app = Flask(__name__)
client = docker.from_env()
logging.basicConfig(level=logging.INFO)

@app.route('/webhook/restart-container', methods=['POST'])
def restart_container():
    """
    Webhook endpoint to automatically restart failing containers.

    Uptime Kuma webhook configuration:
    - URL: http://your-server:5000/webhook/restart-container
    - Method: POST
    - Trigger: On service DOWN
    """
    data = request.json

    # Extract service name from Uptime Kuma webhook payload
    service_name = data.get('monitor', {}).get('name', '')

    # Map monitor names to container names
    container_mapping = {
        "PostgreSQL Database": "postgres",
        "Redis Cache": "redis",
        "API Gateway": "api-gateway"
    }

    container_name = container_mapping.get(service_name)

    if not container_name:
        return jsonify({
            "status": "error",
            "message": f"Unknown service: {service_name}"
        }), 400

    try:
        container = client.containers.get(container_name)

        # Check if container is actually down
        if container.status != "running":
            logging.info(f"Restarting container: {container_name}")
            container.restart()

            return jsonify({
                "status": "success",
                "action": "restarted",
                "container": container_name
            })
        else:
            return jsonify({
                "status": "no_action",
                "message": "Container already running"
            })

    except docker.errors.NotFound:
        return jsonify({
            "status": "error",
            "message": f"Container not found: {container_name}"
        }), 404
    except Exception as e:
        logging.error(f"Error restarting container: {e}")
        return jsonify({
            "status": "error",
            "message": str(e)
        }), 500

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

Deployment:

# docker-compose.yml (add to existing configuration)
services:
  auto-healer:
    build: .
    container_name: uptime-auto-healer
    ports:
      - "5000:5000"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    restart: always

Configure webhook in Uptime Kuma:

  1. Settings → Notifications → Add Notification
  2. Type: Webhook
  3. URL: http://localhost:5000/webhook/restart-container
  4. Method: POST
  5. Assign to monitors that should auto-restart

4. Natural Language Incident Reports

Concept: Generate executive summaries of incidents for stakeholders.

# incident_reporter.py
def generate_incident_summary(incident: dict) -> str:
    """Generate human-readable incident summary using Claude."""

    response = client.messages.create(
        model="claude-opus-4-5-20251101",
        max_tokens=300,
        messages=[{
            "role": "user",
            "content": f"""
            Create a brief incident summary for non-technical stakeholders:

            Service: {incident['service']}
            Duration: {incident['duration_minutes']} minutes
            Start Time: {incident['start_time']}
            End Time: {incident['end_time']}
            Impact: {incident['failed_requests']} requests failed
            Root Cause: {incident['root_cause']}
            Resolution: {incident['resolution']}

            Format: 2-3 sentences suitable for executive update.
            """
        }]
    )

    return response.content[0].text

# Example usage
incident = {
    "service": "API Gateway",
    "duration_minutes": 15,
    "start_time": "2025-12-16 10:23:42",
    "end_time": "2025-12-16 10:38:42",
    "failed_requests": 487,
    "root_cause": "Database connection pool exhaustion",
    "resolution": "Increased connection pool size and restarted service"
}

summary = generate_incident_summary(incident)
print(summary)

Example output:

The API Gateway experienced a 15-minute outage from 10:23 AM to 10:38 AM
due to database connection pool exhaustion. Approximately 487 user requests
failed during this period. The issue was resolved by increasing the connection
pool size and restarting the service, with no expected recurrence.

External Monitoring: Surviving ISP Outages

The Homelab Killer Problem

Scenario: Your ISP goes down. Your entire homelab is offline. Your monitoring system is also offline. How do you know there’s a problem?

Solution: Run Uptime Kuma on external VPS that monitors your homelab via VPN.

Architecture Overview

Your Homelab (192.168.1.0/24)          External VPS ($3-5/month)
─────────────────────────────          ──────────────────────────
├─ Router/Firewall                     ├─ Uptime Kuma
├─ Services (Web, DB, etc.)            ├─ Tailscale/WireGuard VPN
├─ Docker Host                          └─ Monitors homelab via VPN
└─ [Vulnerable to ISP outage]               [Always online, sends alerts]

Cost comparison:

  • Uptime Robot paid: $8-50/month
  • External VPS + Uptime Kuma: $3-5/month
  • Savings: 40-90%

Implementation: VPS with Tailscale

docker-compose.yml on VPS:

version: '3.8'

services:
  uptime-kuma:
    image: louislam/uptime-kuma:1
    container_name: uptime-kuma
    volumes:
      - ./data:/app/data
    ports:
      - "3001:3001"
    restart: always
    network_mode: service:tailscale

  tailscale:
    image: tailscale/tailscale:latest
    container_name: tailscale-vpn
    hostname: kuma-external
    volumes:
      - ./tailscale:/var/lib/tailscale
      - /dev/net/tun:/dev/net/tun
    cap_add:
      - NET_ADMIN
      - SYS_MODULE
    environment:
      - TS_AUTHKEY=${TAILSCALE_AUTHKEY}
      - TS_STATE_DIR=/var/lib/tailscale
      - TS_EXTRA_ARGS=--advertise-tags=tag:monitoring
    restart: always

.env file (create securely):

TAILSCALE_AUTHKEY=tskey-auth-xxxxxxxxxxxxxx

Setup steps:

  1. Get Tailscale auth key:

  2. Deploy on VPS:

    docker compose up -d
  3. Configure homelab Tailscale:

    • Install Tailscale on homelab router or dedicated VM
    • Join same Tailscale network
    • Note Tailscale IPs (e.g., homelab router: 100.64.1.5)
  4. Create monitors in external Uptime Kuma:

    • Monitor homelab services via Tailscale IPs
    • Example: HTTP monitor to http://100.64.1.10:80 (internal web service)

Recommended VPS providers:

  • Hetzner Cloud: €3.79/month for CPX11 (2 vCPU, 2GB RAM)
  • DigitalOcean: $6/month for basic droplet
  • Linode: $5/month for Nanode 1GB
  • Vultr: $5/month for 1 vCPU, 1GB RAM

Troubleshooting Common Issues

Issue 1: Services Showing DOWN When Actually UP

Symptoms: Monitors report DOWN, but manual checks show service is running.

Diagnosis:

# Test connectivity from Uptime Kuma container
docker exec -it uptime-kuma sh
wget -O- http://target-service:port  # Or use curl

# Check DNS resolution
nslookup target-service

# Verify firewall rules
iptables -L | grep target-port

Solutions:

  1. Ensure Docker containers are on same network
  2. Use service hostname (not IP) for Docker services
  3. Check firewall allows outbound connections from Uptime Kuma
  4. Verify target service is actually listening: netstat -tulpn | grep port

Issue 2: Notifications Not Sending

Diagnosis:

# Check Uptime Kuma logs
docker logs uptime-kuma | grep -i notification

# Test webhook manually
curl -X POST https://discord.com/api/webhooks/YOUR_URL   -H "Content-Type: application/json"   -d '{"content":"Test notification"}'

Solutions:

  1. Verify webhook URL is correct (not revoked in Discord/Slack)
  2. Check firewall allows outbound HTTPS (port 443)
  3. Test from container: docker exec uptime-kuma curl https://discord.com
  4. Check notification is assigned to monitor

Issue 3: High Database Storage Usage

Symptoms: Data volume growing > 1GB, slow performance.

Solutions:

# Check database size
du -sh ~/uptime-kuma/data/

# Run shrink database (in Uptime Kuma UI)
# Settings → Maintenance → "Shrink Database"

# Alternative: Manually optimize SQLite
docker exec -it uptime-kuma sh
sqlite3 /app/data/kuma.db "VACUUM;"

Prevention:

  • Reduce check intervals for non-critical services
  • Set data retention policies
  • Consider PostgreSQL for large deployments (1000+ monitors)

Issue 4: WebSocket Errors Behind Reverse Proxy

Symptoms: Real-time updates not working, browser console shows WebSocket errors.

Solution: Verify reverse proxy configuration includes WebSocket headers:

# Critical headers for WebSocket
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";

Test WebSocket:

  1. Open browser DevTools → Network tab
  2. Filter by WS (WebSocket)
  3. Verify connection establishes successfully
  4. Should see 101 Switching Protocols response

Maintenance & Optimization

Regular Maintenance Schedule

Weekly:

  • Review alert patterns for noise
  • Check disk usage: docker system df
  • Verify critical monitors functioning

Monthly:

  • Update Docker image: docker compose pull && docker compose up -d
  • Review and optimize check intervals
  • Test notification delivery
  • Check for security updates

Quarterly:

  • Run “Shrink Database” (Settings → Maintenance)
  • Review all monitors for relevance
  • Test disaster recovery from backup
  • Update documentation

Automated Backups

#!/bin/bash
# backup-uptime-kuma.sh

BACKUP_DIR="/backups/uptime-kuma"
DATA_DIR="/root/uptime-kuma/data"
RETENTION_DAYS=30

# Create backup
mkdir -p "$BACKUP_DIR"
tar czf "$BACKUP_DIR/kuma-$(date +%Y%m%d-%H%M%S).tar.gz" "$DATA_DIR"

# Remove old backups
find "$BACKUP_DIR" -name "kuma-*.tar.gz" -mtime +$RETENTION_DAYS -delete

echo "Backup completed: $(date)"

Schedule with cron:

# Run daily at 2 AM
0 2 * * * /root/backup-uptime-kuma.sh >> /var/log/kuma-backup.log 2>&1

Performance Optimization Tips

Reduce CPU usage:

  • Increase check intervals for non-critical services (5-15 minutes instead of 1 minute)
  • Disable unused notification channels
  • Run on dedicated server (not shared with heavy workloads)

Reduce memory usage:

  • Restart container weekly if memory grows: docker restart uptime-kuma
  • Monitor: docker stats uptime-kuma --no-stream
  • Consider PostgreSQL for better memory management at scale

Reduce storage:

  • Run “Shrink Database” quarterly
  • Delete obsolete monitors
  • Monitor disk usage: du -sh ~/uptime-kuma/data/

Conclusion

You now have a production-ready monitoring system capable of:

  • Tracking availability of websites, APIs, containers, and network services
  • Sending multi-channel alerts via Discord, Telegram, Email, and 90+ other platforms
  • Creating public status pages for transparency
  • Automating responses with AI-powered analysis and self-healing
  • Surviving ISP outages with external monitoring

Next steps:

  1. Start with 3-5 critical services
  2. Configure 2 notification channels
  3. Create your first status page
  4. Experiment with AI automation (optional)
  5. Set up external monitoring for true reliability

Remember: Perfect monitoring is the enemy of good monitoring. Start simple, expand gradually, and focus on truly critical alerts to avoid notification fatigue.


Additional Resources

Official:

Community:

Python API Library:


Last Updated: December 2025 Version Covered: Uptime Kuma 2.0.2+ Word Count: 3,947 words