Operations

Self-host VPS monitoring with Prometheus and Grafana

The first time a VPS goes down at 3 AM and you have no idea what happened, the value of monitoring becomes obvious. Prometheus collects time-series metrics (CPU, memory, disk, network) from your server, and Grafana turns those numbers into dashboards you can glance at on any device. Both self-host on the same VPS without eating much of its resources, a 2 GB RAM instance from thueVPS handles the stack comfortably for a single node. This guide walks through the full setup on Ubuntu 24.04: Prometheus, node_exporter, Grafana, disk-based retention, and alerting via the built-in Alertmanager.

Key takeaways:

  • Collect CPU, RAM, disk, and network metrics via the node_exporter service, no agents, no SaaS middleman.
  • Grafana runs behind an nginx reverse proxy with free Let's Encrypt TLS in under 10 minutes.
  • Disk retention for Prometheus is configurable with a practical 60-day default; adjust retention.time and retention.size to match your VPS disk.
  • Alertmanager routes critical thresholds (disk >90%, node down) through Telegram or email without a third-party monitoring service.

Prerequisites

  • A Linux VPS running Ubuntu 24.04 LTS (this guide uses it; Debian 12 or AlmaLinux 9 work with small path changes).
  • Root or a sudo user on the server.
  • A domain name pointing to the VPS IP, if you want Grafana behind HTTPS (recommended for any dashboard with credentials).
  • At least 1 GB RAM and 10 GB free disk; 2 GB RAM is more comfortable, which matches the Linux VPS VNLite plan from thueVPS.
  • Ports 9090 (Prometheus), 9100 (node_exporter), and 3000 (Grafana) open in the firewall during setup, lock them to trusted IPs or use a reverse proxy when done.

Why self-host monitoring instead of a SaaS dashboard?

A hosted monitoring service like Datadog or New Relic charges per host and per data point. For a single VPS, the monthly cost quickly exceeds the VPS itself. Self-hosting Prometheus and Grafana moves the data to your own disk, gives you full control over retention and alerting rules, and costs nothing beyond the storage and a tiny CPU overhead. The trade-off is maintenance, you own the backup and upgrade schedule. With systemd units and a scripted backup of /var/lib/prometheus, this becomes a 5-minute monthly chore.

自建 Prometheus 与 Grafana 监控 VPS,数据存储本地、告警规则完全可控。

Self-hosting Prometheus and Grafana to monitor a VPS keeps metric data on local disk and alerting rules fully under your control.

Step 1, Install and configure Prometheus

Prometheus is the metric collection engine. It scrapes HTTP endpoints (called "exporters") exposed by the services you want to monitor. The core process is a single statically-linked binary with no external dependencies, which makes it easy to install and maintain.

Create a dedicated system user and download the latest stable release (2.55.x as of early 2026; check the current version at prometheus.io/download and adjust the URL):

sudo useradd --no-create-home --shell /bin/false prometheus
cd /tmp
wget https://github.com/prometheus/prometheus/releases/download/v2.55.2/prometheus-2.55.2.linux-amd64.tar.gz
tar xvf prometheus-2.55.2.linux-amd64.tar.gz
sudo cp prometheus-2.55.2.linux-amd64/prometheus /usr/local/bin/
sudo cp prometheus-2.55.2.linux-amd64/promtool /usr/local/bin/
sudo cp -r prometheus-2.55.2.linux-amd64/consoles /etc/prometheus
sudo cp -r prometheus-2.55.2.linux-amd64/console_libraries /etc/prometheus

Create the configuration and data directories, then write the main config:

sudo mkdir -p /etc/prometheus /var/lib/prometheus
sudo vi /etc/prometheus/prometheus.yml

Paste this minimal config that scrapes Prometheus itself every 15 seconds and stores 60 days of data:

global:
  scrape_interval: 15s
  evaluation_interval: 15s
  retention: 60d

scrape_configs:
  - job_name: 'prometheus'
    static_configs:
      - targets: ['localhost:9090']

The retention key controls how long Prometheus keeps raw data before purging it. On a 20 GB disk, 60 days at 15-second intervals for a single node uses roughly 3-5 GB of storage. If your disk is tight, lower it to 30d.

Set ownership and start Prometheus as a systemd service:

sudo chown -R prometheus:prometheus /etc/prometheus /var/lib/prometheus

sudo tee /etc/systemd/system/prometheus.service > /dev/null <<EOF
[Unit]
Description=Prometheus
Wants=network-online.target
After=network-online.target

[Service]
User=prometheus
Group=prometheus
Type=simple
ExecStart=/usr/local/bin/prometheus \
  --config.file /etc/prometheus/prometheus.yml \
  --storage.tsdb.path /var/lib/prometheus \
  --web.console.templates=/etc/prometheus/consoles \
  --web.console.libraries=/etc/prometheus/console_libraries \
  --web.listen-address=0.0.0.0:9090

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable --now prometheus

Verify, the Prometheus web UI should respond on port 9090:

curl http://localhost:9090/targets
# Expected: a JSON list of targets; look for "state":"up"

Step 2, Install node_exporter for system metrics

node_exporter exposes hardware and OS metrics on port 9100: CPU usage broken down by mode (user, system, iowait), memory (total, free, buffers, cache), disk I/O per device, network bytes and errors per interface, filesystem usage, and load averages. Prometheus scrapes it like any other exporter.

cd /tmp
wget https://github.com/prometheus/node_exporter/releases/download/v1.8.2/node_exporter-1.8.2.linux-amd64.tar.gz
tar xvf node_exporter-1.8.2.linux-amd64.tar.gz
sudo cp node_exporter-1.8.2.linux-amd64/node_exporter /usr/local/bin/
sudo useradd --no-create-home --shell /bin/false node_exporter

sudo tee /etc/systemd/system/node_exporter.service > /dev/null <<EOF
[Unit]
Description=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=:9100 \
  --path.rootfs=/

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable --now node_exporter

Verify, the exporter returns all system metrics as plain text:

curl http://localhost:9100/metrics | head -20
# Expected: lines starting with node_cpu_seconds_total, node_memory_MemTotal_bytes, node_filesystem_avail_bytes

Now tell Prometheus to scrape node_exporter. Edit the config:

sudo vi /etc/prometheus/prometheus.yml

Add a second job under scrape_configs:

  - job_name: 'node'
    static_configs:
      - targets: ['localhost:9100']

Restart Prometheus:

sudo systemctl restart prometheus

Wait 30 seconds, then check that the new target appears as "UP" at http://your-vps-ip:9090/targets.

Step 3, Install Grafana behind nginx with HTTPS

Grafana reads Prometheus as a data source and renders dashboards. By default, Grafana listens on port 3000 without TLS. Exposing that port to the internet is fine for a single-user setup if you lock it with a firewall rule, but a reverse proxy with Let's Encrypt is cleaner and lets you reach the dashboard over port 443.

Install Grafana from the official repository:

sudo apt install -y software-properties-common
sudo add-apt-repository "deb https://packages.grafana.com/oss/deb stable main"
wget -q -O - https://packages.grafana.com/gpg.key | sudo apt-key add -
sudo apt update
sudo apt install -y grafana

sudo systemctl enable --now grafana-server

Verify, Grafana HTTP listens on port 3000:

sudo ss -tlnp | grep 3000
# Expected: grafana-server listening on 0.0.0.0:3000

Set up nginx as a reverse proxy with Certbot for free TLS:

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

sudo tee /etc/nginx/sites-available/grafana > /dev/null <<EOF
server {
    listen 80;
    server_name grafana.yourdomain.com;

    location / {
        proxy_pass http://localhost:3000;
        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;
    }
}
EOF

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

sudo certbot --nginx -d grafana.yourdomain.com
# Follow the interactive prompt; a free DV certificate from Let's Encrypt is issued.

Visit https://grafana.yourdomain.com, the default login is admin / admin (change it immediately).

Inside Grafana, go to Configuration → Data Sources → Add data source → Prometheus. Set the URL to http://localhost:9090 and click Save & Test. A green "Data source is working" confirms the connection.

Step 4, Create a basic system dashboard

Grafana includes the official Node Exporter Full dashboard (ID 1860 on grafana.com/grafana/dashboards). Import it without building from scratch:

  • Hover over the + icon on the left sidebar → Import.
  • Enter 1860 in the "Import via grafana.com" field → click Load.
  • Select the Prometheus data source and Import.

The dashboard loads panels for CPU (by core and overall), memory (total, used, cached, available), disk usage per mount point, network throughput and errors, and load averages. All panels query the node_exporter metrics through Prometheus.

If you prefer a minimal custom dashboard with just CPU and memory, create a new dashboard, add a panel with a PromQL query like 100 - (avg by(instance)(rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) for CPU usage, and another with node_memory_Active_bytes / node_memory_MemTotal_bytes * 100 for memory. Set a bar gauge or time-series visualisation.

Step 5, Alerting with Alertmanager

Prometheus evaluates alerting rules and fires alerts to Alertmanager, which handles deduplication, grouping, and routing to Telegram, email, Slack, or a webhook. Setting up a full alerting pipeline requires three pieces: an alerting rule file, an Alertmanager config, and a receiver (Telegram in this example).

Create a rules file for the node exporter:

sudo vi /etc/prometheus/alert-rules.yml

Define two basic rules, high disk usage and a dead node:

groups:
  - name: node_alerts
    rules:
      - alert: HighDiskUsage
        expr: (node_filesystem_avail_bytes{mountpoint="/"} / node_filesystem_size_bytes{mountpoint="/"}) * 100 < 10
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "Disk usage above 90% on {{ $labels.instance }}"

      - alert: NodeDown
        expr: up{job="node"} == 0
        for: 2m
        labels:
          severity: critical
        annotations:
          summary: "Node {{ $labels.instance }} is down"

Reference the rules file in /etc/prometheus/prometheus.yml:

rule_files:
  - 'alert-rules.yml'

Restart Prometheus:

sudo systemctl restart prometheus

Now install and configure Alertmanager (separate binary, same pattern):

cd /tmp
wget https://github.com/prometheus/alertmanager/releases/download/v0.28.1/alertmanager-0.28.1.linux-amd64.tar.gz
tar xvf alertmanager-0.28.1.linux-amd64.tar.gz
sudo cp alertmanager-0.28.1.linux-amd64/alertmanager /usr/local/bin/
sudo cp alertmanager-0.28.1.linux-amd64/amtool /usr/local/bin/

sudo useradd --no-create-home --shell /bin/false alertmanager
sudo mkdir -p /etc/alertmanager /var/lib/alertmanager

sudo vi /etc/alertmanager/alertmanager.yml

Paste a Telegram receiver config (replace YOUR_BOT_TOKEN and YOUR_CHAT_ID):

route:
  receiver: 'telegram-admin'
  group_wait: 30s
  group_interval: 5m
  repeat_interval: 4h

receivers:
  - name: 'telegram-admin'
    telegram_configs:
      - bot_token: 'YOUR_BOT_TOKEN'
        chat_id: YOUR_CHAT_ID
        send_resolved: true

Create a systemd unit for Alertmanager:

sudo tee /etc/systemd/system/alertmanager.service > /dev/null <<EOF
[Unit]
Description=Alertmanager
Wants=network-online.target
After=network-online.target

[Service]
User=alertmanager
Group=alertmanager
Type=simple
ExecStart=/usr/local/bin/alertmanager \
  --config.file=/etc/alertmanager/alertmanager.yml \
  --storage.path=/var/lib/alertmanager

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable --now alertmanager

Finally, tell Prometheus to send alerts to Alertmanager. Add this block to /etc/prometheus/prometheus.yml:

alerting:
  alertmanagers:
    - static_configs:
        - targets: ['localhost:9093']

Restart Prometheus one last time. Any matching condition fires an alert within minutes.

Troubleshooting

Prometheus won't start, check the logs with journalctl -u prometheus --no-pager -n 30. A common error is a YAML indentation mistake in prometheus.yml. Validate the config with promtool check config /etc/prometheus/prometheus.yml.

Grafana says "Data source connected but no data", verify that Prometheus is scraping node_exporter: visit http://vps-ip:9090/targets and check the "node" target state. If it shows "DOWN", node_exporter may have died. Restart it: sudo systemctl restart node_exporter.

Alerts fire but never reach Telegram, Alertmanager logs tell the story: journalctl -u alertmanager --no-pager -n 50. The two most common issues: the bot token is wrong, or the chat ID is negative and you omitted the - sign (group chats use negative IDs).

Disk space is filling up from Prometheus data, Edit /etc/prometheus/prometheus.yml and add or lower the retention.size line, e.g. retention.size: 5GB. Restart Prometheus. This removes the oldest blocks first.

FAQ

What is the minimum VPS spec for Prometheus and Grafana?

A 1 vCPU, 1 GB RAM VPS runs the stack for a single monitored node, but 2 GB RAM is recommended for a responsive Grafana UI. The VNLite plan from thueVPS at 2 GB RAM and 20 GB NVMe for a low monthly fee works well.

Can I monitor multiple VPSes with this setup?

Yes. Install node_exporter on every additional server (any Linux distribution) and add its IP:9100 to the scrape_configs in prometheus.yml. One Prometheus instance can scrape hundreds of targets.

How do I secure Grafana behind the reverse proxy?

Set a strong password (Grafana → Server Admin → Users → edit your admin account) and enable HTTPS through the nginx reverse proxy as shown above. Consider adding IP-based access restrictions to /etc/nginx/sites-available/grafana with allow YOUR_IP; deny all; if you are the only user.

Does Prometheus support long-term storage?

Prometheus is designed for short to medium retention (weeks to months). For years of history, use Thanos or a remote write backend like VictoriaMetrics. The retention.time and retention.size settings in the config handle the Prometheus side.

Can I use this stack to monitor Docker containers?

Yes. Add cadvisor as an exporter on each Docker host (a container that exposes container-level metrics) and scrape it from Prometheus. Grafana has a dedicated Docker monitoring dashboard (ID 179 on grafana.com).

Related articles

VPS 监控:Prometheus 与 Grafana 自建部署

在 Linux VPS 上自建 Prometheus 和 Grafana,可以在本地存储 60 天的系统指标(CPU、内存、磁盘、网络),无需 SaaS 订阅费用。部署过程包括安装 Prometheus 核心、node_exporter 收集器、Grafana 可视化面板以及 Alertmanager 告警引擎。通过 Telegram 或邮箱接收磁盘超过 90%、节点离线等告警。建议选择 2 GB 内存以上的 VPS,例如 thueVPS 入门款每月仅 18 万越南盾起。注意定期备份 /var/lib/prometheus 目录以保证历史数据不丢失。

Note: This guide is for general reference. Every system and infrastructure has its own specifics, so test each step in a safe environment and consult a qualified engineer before applying it in production.