How to Monitor Server Uptime with Free Tools

by David Park
How to Monitor Server Uptime with Free Tools

What You'll End Up With

By the end of this guide you'll have three layers of uptime monitoring running at no cost: a self-hosted Uptime Kuma instance on your VPS sending alerts to Telegram, a free UptimeRobot account providing an external check from outside your network, and a cron-based local health check that pages you when a process dies. Together these cover the blind spots each individual tool has. If your server goes dark, at least one layer will catch it.

Prerequisites

  • Ubuntu 24.04 VPS (1 vCPU / 1 GB RAM minimum; a Hetzner CAX11 at €3.79/mo works fine)
  • A non-root sudo user
  • Docker and Docker Compose v2 installed (docker --version ≥ 26.0)
  • A domain or subdomain pointed at the VPS (e.g. status.example.com)
  • A Telegram bot token and chat ID (free — covered in Step 3)
  • A free UptimeRobot account at uptimerobot.com
  • Port 3001 open in your firewall (or you'll reverse-proxy it)

Step 1 — Deploy Uptime Kuma with Docker Compose

Uptime Kuma is a self-hosted, MIT-licensed monitoring tool with a clean dashboard and built-in alerting. Deploying it takes four commands.

1.1 — Create the project directory.

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

Keeps all config and data in one place.

1.2 — Write the Compose file.

cat > docker-compose.yml << 'EOF'
services:
  uptime-kuma:
    image: louislam/uptime-kuma:1.23.13
    container_name: uptime-kuma
    restart: unless-stopped
    ports:
      - "127.0.0.1:3001:3001"
    volumes:
      - ./data:/app/data
EOF

Binding to 127.0.0.1 keeps the port off the public internet; Nginx will proxy it.

1.3 — Start the container.

docker compose up -d

Expected output:

[+] Running 2/2
 ✔ Network uptime-kuma_default  Created
 ✔ Container uptime-kuma        Started

1.4 — Confirm it's listening.

curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:3001

Expected output: 200


Step 2 — Reverse-Proxy Uptime Kuma with Nginx

Expose Kuma over HTTPS so external checks and your browser can reach it securely.

2.1 — Install Nginx and Certbot.

sudo apt update && sudo apt install -y nginx python3-certbot-nginx

2.2 — Write the Nginx server block.

sudo tee /etc/nginx/sites-available/uptime-kuma << 'EOF'
server {
    listen 80;
    server_name status.example.com;

    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_cache_bypass $http_upgrade;
    }
}
EOF

Replace status.example.com with your actual subdomain.

2.3 — Enable the site and reload Nginx.

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

Expected output from nginx -t:

nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

2.4 — Issue a TLS certificate.

sudo certbot --nginx -d status.example.com --non-interactive --agree-tos -m you@example.com

Certbot rewrites the server block to add SSL and sets up auto-renewal via a systemd timer.


Step 3 — Configure Telegram Alerts in Uptime Kuma

Uptime Kuma needs a notification channel before monitors are useful. Telegram is free and delivers alerts in under five seconds.

3.1 — Create a Telegram bot.

Open Telegram, search for @BotFather, send /newbot, and follow the prompts. Copy the token (format: 123456789:ABCdef...).

3.2 — Get your chat ID.

curl -s "https://api.telegram.org/bot<YOUR_TOKEN>/getUpdates" | python3 -m json.tool | grep '"id"' | head -5

Send any message to your bot first, then run this. Your numeric chat ID appears next to "id".

3.3 — Add the notification in Kuma's UI.

  1. Open https://status.example.com and create your admin account on first login.
  2. Go to Settings → Notifications → Add Notification.
  3. Select Telegram, paste your bot token and chat ID, click Test then Save.

You'll receive a test message in Telegram if the credentials are correct.

3.4 — Add your first monitor.

  1. Click Add New Monitor.
  2. Type: HTTP(s), URL: your production domain, Heartbeat interval: 60 seconds.
  3. Assign the Telegram notification, click Save.

Kuma starts polling immediately and shows response time history.


Step 4 — Add an External Check with UptimeRobot

Uptime Kuma runs on the same server it monitors. If the whole VPS goes down, Kuma goes down too. UptimeRobot pings your site from external nodes — that's the gap it fills.

4.1 — Create a free UptimeRobot account.

Go to uptimerobot.com, sign up with your email. The free tier gives you 50 monitors with 5-minute intervals — more than enough.

4.2 — Add an HTTP monitor.

  1. Click + Add New Monitor.
  2. Monitor Type: HTTP(s).
  3. Friendly Name: e.g. My App - Production.
  4. URL: your app's URL.
  5. Monitoring Interval: 5 minutes.
  6. Click Create Monitor.

4.3 — Configure email and webhook alerts.

In the monitor's Alert Contacts section, add your email (already set up by default). Optionally add a webhook pointing to your Kuma instance's webhook endpoint for a unified alert trail.

4.4 — Grab your public status page.

UptimeRobot generates a free hosted status page at stats.uptimerobot.com/XXXXXXXX. Share it with customers — zero hosting cost on your end.


Step 5 — Add a Local Cron Health Check

External HTTP checks don't catch a crashed process that leaves port 80 open via Nginx's cached response. A local cron job checks the process directly.

5.1 — Write the health check script.

sudo tee /usr/local/bin/healthcheck.sh << 'EOF'
#!/usr/bin/env bash
set -euo pipefail

TOKEN="<YOUR_TELEGRAM_BOT_TOKEN>"
CHAT_ID="<YOUR_CHAT_ID>"
HOST=$(hostname)

alert() {
  curl -s -X POST "https://api.telegram.org/bot${TOKEN}/sendMessage" \
    -d chat_id="${CHAT_ID}" \
    -d text="[${HOST}] ALERT: $1"
}

# Check Docker container
if ! docker inspect -f '{{.State.Running}}' uptime-kuma 2>/dev/null | grep -q true; then
  alert "uptime-kuma container is not running"
fi

# Check Nginx
if ! systemctl is-active --quiet nginx; then
  alert "nginx is not active"
fi

# Check disk usage (alert if > 85%)
DISK=$(df / | awk 'NR==2 {print $5}' | tr -d '%')
if [ "${DISK}" -gt 85 ]; then
  alert "Disk usage at ${DISK}% on /"
fi
EOF
sudo chmod +x /usr/local/bin/healthcheck.sh

Replace the token and chat ID placeholders with your real values.

5.2 — Schedule it with cron.

sudo crontab -e

Add this line at the bottom:

*/5 * * * * /usr/local/bin/healthcheck.sh >> /var/log/healthcheck.log 2>&1

Runs every five minutes as root so it can inspect Docker and systemd.

5.3 — Test the script manually.

sudo /usr/local/bin/healthcheck.sh

No output means all checks passed. Stop Nginx temporarily to confirm the alert fires:

sudo systemctl stop nginx
sudo /usr/local/bin/healthcheck.sh
sudo systemctl start nginx

You should receive a Telegram message within seconds.


Verify It Works

Run through this checklist after completing all steps:

# 1. Kuma container running
docker ps --filter name=uptime-kuma --format "{{.Status}}"
# Expected: Up X minutes

# 2. HTTPS endpoint reachable
curl -s -o /dev/null -w "%{http_code}" https://status.example.com
# Expected: 200

# 3. Cron job registered
sudo crontab -l | grep healthcheck
# Expected: */5 * * * * /usr/local/bin/healthcheck.sh ...

# 4. TLS certificate valid
echo | openssl s_client -connect status.example.com:443 2>/dev/null | openssl x509 -noout -dates
# Expected: notAfter date ~90 days out

Log into UptimeRobot and confirm the monitor shows Up with a green badge. In Kuma's dashboard, your HTTP monitor should show a green heartbeat.


Troubleshooting

Kuma dashboard returns 502 Bad Gateway The container isn't running or isn't bound to port 3001. Run docker compose -f ~/uptime-kuma/docker-compose.yml ps and check docker logs uptime-kuma.

Certbot fails with "Could not bind to IPv4 or IPv6" Nginx is occupying port 80. Run sudo systemctl stop nginx, issue the certificate, then sudo systemctl start nginx.

Telegram alerts not arriving Verify the token with curl "https://api.telegram.org/bot<TOKEN>/getMe". A valid token returns a JSON object with "ok":true. Double-check you sent the bot a message before fetching the chat ID — bots can't initiate conversations.

UptimeRobot shows the site as down even though it's up Check whether your firewall blocks UptimeRobot's IP ranges. Their IPs are listed at uptimerobot.com/help/ip-addresses. Add them to your ufw allow rules if needed.

Cron script runs but no alert fires on failure Confirm curl is installed (which curl) and that the script is executable (ls -l /usr/local/bin/healthcheck.sh). Review /var/log/healthcheck.log for error output.

Disk usage alert fires on every run Your disk is genuinely above 85%. Run df -h / to confirm, then du -sh /var/log/* /var/lib/docker to find the culprit.


Next Steps

You now have three independent layers to monitor server uptime with free tools: Uptime Kuma for internal visibility and history, UptimeRobot for external validation, and a cron script for process-level checks. From here you can:

  • Add SSL certificate expiry monitors in Kuma (type: Certificate Info).
  • Push Kuma metrics to a Grafana Cloud free-tier instance for long-term retention.
  • Extend the cron script to check memory pressure with free -m and alert on swap usage.
  • Set up a Kuma status page (Settings → Status Page) as a customer-facing alternative to UptimeRobot's hosted page.

The total monthly cost remains $0 beyond your existing VPS bill.