Back to tutorials
Tutorial

VPS monitoring setup tutorial (2026): Uptime Kuma + Node Exporter + Alerting for Linux servers

VPS monitoring setup tutorial for Linux: uptime checks, metrics, and alerts with Uptime Kuma + node_exporter in 2026.

By Anurag Singh
Updated on Sep 19, 2026
Category: Tutorial
Share article
VPS monitoring setup tutorial (2026): Uptime Kuma + Node Exporter + Alerting for Linux servers

You can run a “stable” VPS for months and still miss what actually takes you down: slow failure. Disks creep toward 100%. TLS renewals quietly fail. Load spikes hit at 3 a.m. This VPS monitoring setup tutorial shows a practical 2026 monitoring stack. It covers uptime checks and real Linux metrics, without turning monitoring into a second job.

You’ll set up:

  • Uptime Kuma for HTTP/HTTPS, TCP, and keyword checks (plus notifications).
  • Prometheus node_exporter for Linux metrics (CPU, RAM, disk, network).
  • A small set of actionable alerts tied to common hosting failures: low disk, OOM risk, certificate expiry, and sustained high load.

This guide assumes Ubuntu 24.04 LTS or Debian 12/13 on a VPS. The same approach works on AlmaLinux/Rocky with small package-name changes.

What you’ll build (and why this is the right scope for hosting)

Most hosting stacks don’t need a full observability suite. They need a few tight feedback loops. Each loop should answer a question you ask during an incident.

  • Is the site reachable? (HTTP status, TLS validity, response time)
  • Is the server healthy? (disk headroom, memory pressure, load spikes)
  • Is something changing? (unexpected restarts, sudden traffic bursts, bot spikes)

Uptime Kuma answers reachability fast. node_exporter covers machine health.

Then add only the alerts you will actually act on.

If you’re running client sites, reseller hosting, or several WordPress installs, start with a separate monitoring VPS. Keep it small now, then scale later.

HostMyCode makes that straightforward: begin on a HostMyCode VPS and move to HostMyCode dedicated servers once you outgrow shared resources.

Prerequisites and a safe layout

Recommended layout:

  • One small VPS for monitoring (1 vCPU / 1–2 GB RAM is fine for a handful of nodes).
  • Put monitoring on its own DNS name, like status.example.com or monitor.example.com.
  • Restrict access: only your IPs, a VPN, or an SSO reverse proxy if you have one.

Before you start on the monitoring VPS:

  • Update packages: sudo apt update && sudo apt -y upgrade
  • Confirm time sync: timedatectl (alerts get confusing fast on skewed clocks)

If you haven’t hardened SSH, do that first. This tutorial assumes you can administer the box without exposing weak logins.

If you want a current checklist, see this SSH lockdown tutorial.

Step 1: Install Docker Engine on the monitoring VPS (Ubuntu/Debian)

Uptime Kuma runs cleanly in a container. Docker also keeps upgrades predictable.

You can roll forward (or back) without hand-editing the app.

sudo apt update
sudo apt -y install ca-certificates curl gnupg

sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg

echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] 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 -y install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo systemctl enable --now docker

Optional but convenient:

sudo usermod -aG docker $USER
# Log out and back in for group change to apply

Step 2: Deploy Uptime Kuma with Docker Compose

Use a dedicated directory and a persistent data volume. This keeps monitors, status pages, and notification settings across upgrades.

sudo mkdir -p /opt/uptime-kuma
cd /opt/uptime-kuma

Create /opt/uptime-kuma/compose.yml:

services:
  uptime-kuma:
    image: louislam/uptime-kuma:1
    container_name: uptime-kuma
    restart: unless-stopped
    ports:
      - "3001:3001"
    volumes:
      - ./data:/app/data

Start it:

sudo docker compose up -d
sudo docker ps

Open http://YOUR_MONITORING_VPS_IP:3001 and create your admin user.

Quick diagnostic: If the UI doesn’t load, confirm the port is listening. Then check container health:

  • sudo ss -lntp | grep 3001
  • sudo docker logs --tail=100 uptime-kuma

Step 3: Put Uptime Kuma behind HTTPS (Nginx reverse proxy)

Don’t leave your monitoring UI on plain HTTP. Use Nginx on the host and proxy to the container.

Install Nginx:

sudo apt -y install nginx
sudo systemctl enable --now nginx

Create a server block at /etc/nginx/sites-available/monitor.example.com:

server {
  listen 80;
  server_name monitor.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_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;
  }
}

Enable it:

sudo ln -s /etc/nginx/sites-available/monitor.example.com /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

Issue a Let’s Encrypt certificate (Certbot):

sudo apt -y install certbot python3-certbot-nginx
sudo certbot --nginx -d monitor.example.com

Now sign in at https://monitor.example.com.

If you later hit renewal/DCV issues, keep this guide bookmarked: VPS SSL setup guide.

Step 4: Lock down access (you don’t want your status panel indexed)

Even with a login page, reduce exposure. Treat this as an admin panel, not a public site.

Two options cover most setups.

Option A: IP allowlist in Nginx (fast and effective)

Add this inside the location / block:

allow 203.0.113.10;   # your office IP
allow 198.51.100.0/24; # your VPN subnet
deny all;

Reload:

sudo nginx -t && sudo systemctl reload nginx

Option B: Basic auth (good fallback when your IP changes)

sudo apt -y install apache2-utils
sudo htpasswd -c /etc/nginx/.htpasswd monitoradmin

Then add to the server block:

auth_basic "Restricted";
auth_basic_user_file /etc/nginx/.htpasswd;

Step 5: Add your first monitors (the ones that catch real hosting failures)

In Uptime Kuma, build monitors around issues users actually feel. That usually means broken pages, timeouts, and “it loads but it’s wrong.”

  • HTTP(s) monitor for the homepage and checkout page (WooCommerce): checks status code and latency.
  • Keyword monitor for a stable string like “My Account” or a unique footer line: catches app errors that still return 200.
  • TCP port monitor for 22 (SSH) and 443 (HTTPS): helps separate “host unreachable” from app/DNS problems.

Good defaults (2026):

  • Interval: 60 seconds for money pages, 120–300 seconds for everything else.
  • Retries: 2–3 with a short retry interval.
  • Heartbeat checks for cron-based jobs (backups, renewals) if you can emit a ping.

If you’re migrating sites, add monitors before DNS cutover. You’ll catch wrong content or missing assets quickly.

Pair that with a restore drill so rollback isn’t theoretical: VPS restore drill tutorial.

Step 6: Configure notifications (email + a noisy channel)

Uptime Kuma supports plenty of notification backends. Don’t overthink it.

Pick at least two channels so one can fail without taking alerts with it.

  • Email (audit trail, forwarding rules, business-owner visibility)
  • Telegram/Slack/Discord (for whoever is on-call right now)

If you send alerts through a VPS you manage, treat mail deliverability as part of the monitoring system. Alerts that land in spam might as well not exist.

Verify SPF/DKIM/DMARC and reverse DNS first. This is a solid baseline: VPS email setup tutorial.

Practical tip: In Kuma, use a clear subject prefix like [ALERT] and a from name like Monitoring. This makes filters and escalation rules easier.

Step 7: Install node_exporter on each Linux server you want to monitor

Uptime checks tell you what’s broken. Metrics tell you what’s about to break.

In practice, metrics often give hours or days of warning.

On each target VPS/dedicated server (Ubuntu/Debian), create a dedicated user:

sudo useradd --no-create-home --shell /usr/sbin/nologin node_exporter

Download node_exporter (choose the current stable from the official Prometheus releases page). Example pattern:

cd /tmp
NODE_EXPORTER_VERSION="1.9.0"
curl -LO "https://github.com/prometheus/node_exporter/releases/download/v${NODE_EXPORTER_VERSION}/node_exporter-${NODE_EXPORTER_VERSION}.linux-amd64.tar.gz"
tar xzf "node_exporter-${NODE_EXPORTER_VERSION}.linux-amd64.tar.gz"
sudo cp "node_exporter-${NODE_EXPORTER_VERSION}.linux-amd64/node_exporter" /usr/local/bin/
sudo chown node_exporter:node_exporter /usr/local/bin/node_exporter

Create a systemd unit: /etc/systemd/system/node_exporter.service

[Unit]
Description=Prometheus Node Exporter
Wants=network-online.target
After=network-online.target

[Service]
User=node_exporter
Group=node_exporter
Type=simple
ExecStart=/usr/local/bin/node_exporter \
  --web.listen-address=127.0.0.1:9100
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target

Start it:

sudo systemctl daemon-reload
sudo systemctl enable --now node_exporter
sudo systemctl status node_exporter --no-pager

Why bind to 127.0.0.1? You shouldn’t leave port 9100 exposed.

Instead, publish it safely via a proxy, tunnel, or VPN.

Step 8: Expose node_exporter safely (Nginx + firewall)

There are two common, hosting-friendly patterns here. Pick one and use it everywhere.

Consistency makes troubleshooting faster.

Option A: Nginx reverse proxy with IP allowlist (recommended)

On the target server, install Nginx if it isn’t already present. Then create /etc/nginx/sites-available/node-exporter:

server {
  listen 9101;
  server_name _;

  location / {
    allow 203.0.113.10; # monitoring VPS IP
    deny all;

    proxy_pass http://127.0.0.1:9100;
    proxy_set_header Host $host;
  }
}

Enable and reload:

sudo ln -s /etc/nginx/sites-available/node-exporter /etc/nginx/sites-enabled/node-exporter
sudo nginx -t
sudo systemctl reload nginx

Now your monitoring server can scrape http://TARGET_IP:9101/metrics. It will work only if the request matches the allowlist.

Option B: SSH tunnel (best when servers have no public metrics exposure)

From the monitoring VPS:

ssh -N -L 19100:127.0.0.1:9100 root@TARGET_SERVER_IP

This works, but you must keep the tunnel alive (systemd user service or autossh).

For most hosting operations, Option A is easier to maintain.

Step 9: Add a minimal Prometheus to store metrics

node_exporter publishes metrics. Prometheus collects and stores them.

At small scale, Prometheus stays lightweight and easy to operate.

On the monitoring VPS:

sudo mkdir -p /opt/prometheus/{data,config}
cd /opt/prometheus

Create /opt/prometheus/config/prometheus.yml:

global:
  scrape_interval: 15s

scrape_configs:
  - job_name: "node"
    static_configs:
      - targets:
          - "203.0.113.50:9101" # web-vps-1
          - "203.0.113.51:9101" # mail-vps-1
        labels:
          env: "production"

Create /opt/prometheus/compose.yml:

services:
  prometheus:
    image: prom/prometheus:v2.55.0
    container_name: prometheus
    restart: unless-stopped
    ports:
      - "9090:9090"
    volumes:
      - ./config/prometheus.yml:/etc/prometheus/prometheus.yml:ro
      - ./data:/prometheus

Start it:

sudo docker compose up -d
sudo docker logs --tail=50 prometheus

Visit http://YOUR_MONITORING_VPS_IP:9090 and run a query like:

node_filesystem_avail_bytes

Pitfall: If you get no results, confirm the metrics endpoint is reachable from the monitoring VPS:

curl -sS http://203.0.113.50:9101/metrics | head

Step 10: Add a few alerts that map to hosting outages

Use Uptime Kuma for “it’s down” events. Use Prometheus for “it’s getting risky” signals.

Prometheus typically sends alerts via Alertmanager. Start with a small rule set. Expand after you’ve lived with it.

Deploy Alertmanager (optional but useful if you want proper routing):

sudo mkdir -p /opt/alertmanager/{data,config}
cd /opt/alertmanager

Create /opt/alertmanager/config/alertmanager.yml (email example):

route:
  receiver: "email"
receivers:
  - name: "email"
    email_configs:
      - to: "ops@example.com"
        from: "monitor@example.com"
        smarthost: "mail.example.com:587"
        auth_username: "monitor@example.com"
        auth_password: "YOUR_APP_PASSWORD"
        require_tls: true

Create /opt/alertmanager/compose.yml:

services:
  alertmanager:
    image: prom/alertmanager:v0.28.0
    container_name: alertmanager
    restart: unless-stopped
    ports:
      - "9093:9093"
    volumes:
      - ./config/alertmanager.yml:/etc/alertmanager/alertmanager.yml:ro
      - ./data:/alertmanager

Start it:

sudo docker compose up -d

Now add alert rules to Prometheus. Create /opt/prometheus/config/alerts.yml:

groups:
- name: node-health
  rules:
  - alert: HostDiskLow
    expr: (node_filesystem_avail_bytes{fstype!~"tmpfs|overlay"} / node_filesystem_size_bytes{fstype!~"tmpfs|overlay"}) < 0.15
    for: 10m
    labels:
      severity: warning
    annotations:
      summary: "Disk low on {{ $labels.instance }}"
      description: "Free disk is below 15% for 10 minutes. Check /var logs, backups, and uploads."

  - alert: HostMemoryPressure
    expr: (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) < 0.10
    for: 5m
    labels:
      severity: critical
    annotations:
      summary: "Memory pressure on {{ $labels.instance }}"
      description: "MemAvailable below 10%. Expect OOM kills, PHP-FPM crashes, or mail delays."

  - alert: HostLoadHigh
    expr: node_load15 > (count without(cpu, mode) (node_cpu_seconds_total{mode="system"}) ) * 1.5
    for: 10m
    labels:
      severity: warning
    annotations:
      summary: "Sustained high load on {{ $labels.instance }}"
      description: "15-min load is high relative to CPU cores. Check slow requests, bots, cron, or backups."

Wire the alert rules and Alertmanager into prometheus.yml:

rule_files:
  - /etc/prometheus/alerts.yml

alerting:
  alertmanagers:
    - static_configs:
        - targets: ["alertmanager:9093"]

Update /opt/prometheus/compose.yml to mount alerts and connect both services on the same Docker network (simplest is a shared compose file, but you can also create a user-defined network).

Then restart Prometheus:

cd /opt/prometheus
sudo docker compose down
sudo docker compose up -d

Sanity check: In Prometheus, open Status → Rules and confirm the alert group loads.

Step 11: Add “certificate expiry” and “backup heartbeat” checks in Uptime Kuma

Two monitors prevent the most annoying “how did we miss that?” outages:

  • TLS certificate expiry: Uptime Kuma can warn before expiry. Set a threshold like 14 days to catch renewal failures early.
  • Backup heartbeat: Create a heartbeat monitor and have your backup script ping it after a successful run.

Example heartbeat in a backup script (runs on the server being backed up):

curl -fsS "https://monitor.example.com/api/push/YOUR_PUSH_TOKEN?status=up&msg=backup_ok"

If you’re still designing backups, don’t rely on assumptions. Define RPO/RTO and keep offsite copies.

Start here: VPS backup strategy tutorial.

Step 12: Operational checklist (what to review weekly)

Monitoring only pays off if you review it. This weekly routine stays short, but it catches most slow failures.

  • Scan Uptime Kuma for intermittent downtimes (flapping is a clue).
  • In Prometheus, graph disk free % for each node. Confirm it trends flat, not down.
  • Confirm TLS expiry checks show > 14 days for public sites.
  • Confirm backup heartbeat fired on schedule.
  • Pick one node and run: journalctl -p warning -S -7d --no-pager | head -200

If you want a daily summary email, Logwatch still does the job and pairs well with dashboards.

See Logwatch setup.

Common problems and fast fixes

Prometheus can’t scrape targets

  • From monitoring VPS: curl -sS http://TARGET_IP:9101/metrics | head
  • On target: sudo ss -lntp | egrep '9100|9101'
  • Check Nginx allowlist: is the monitoring VPS IP correct?
  • Firewall: ensure only the monitoring VPS can reach 9101 (UFW example: sudo ufw allow from MONITOR_IP to any port 9101 proto tcp)

Uptime Kuma says “down” but the site is fine

  • Check for WAF/rate-limit blocks triggered by frequent checks.
  • Switch to keyword checks on a lightweight endpoint.
  • Bump timeout slightly if the origin relies on heavy PHP pages.

If bots and brute force are part of the story, monitoring won’t solve it by itself. Pair it with protection and tune it carefully: ModSecurity + OWASP CRS tutorial.

Summary: keep it small, keep it useful

This VPS monitoring setup tutorial gives you a baseline that matches day-to-day hosting. Uptime Kuma tells you what’s down. node_exporter + Prometheus show what’s trending toward an outage. A small alert set keeps noise under control.

If you want a clean place to run this stack (or you’re consolidating multiple client sites), start with managed VPS hosting from HostMyCode.

You get a predictable Linux environment, stable networking, and room to grow without reworking monitoring every few months.

If you run multiple sites on a VPS, you want early warnings—not customer screenshots. HostMyCode’s VPS plans work well for lightweight monitoring nodes and production servers, and managed VPS hosting is there if you’d rather hand off patching and the day-to-day care of the box.

FAQ

Do I need Grafana for this setup?

Not to start. Prometheus’ built-in graphing plus Uptime Kuma covers most small hosting setups. Add Grafana only when you need dashboards for many stakeholders.

Should I monitor from the same VPS I’m monitoring?

No. If the VPS goes down, your monitoring disappears with it. A small separate monitoring VPS is the simplest form of redundancy.

How many checks is “too many” in Uptime Kuma?

For a small VPS, keep high-frequency checks to money pages (every 60 seconds) and run the rest every 2–5 minutes. Too many checks can trigger rate limits or WAF rules.

Is node_exporter safe to expose on the internet?

Don’t expose it publicly. Bind it to 127.0.0.1 and proxy it with an IP allowlist, or scrape it through a tunnel/VPN.

What’s the first alert I should set up?

Low disk. It’s one of the most common causes of “sudden” outages on hosting servers because it breaks logs, uploads, backups, and sometimes databases.

VPS monitoring setup tutorial (2026): Uptime Kuma + Node Exporter + Alerting for Linux servers | HostMyCode