Prometheus, Grafana and Alertmanager on a single VPS: a Kubernetes-free weekend guide
Self-hosted monitoring on a single Ubuntu 24.04 VPS: Prometheus, node_exporter, Grafana and an alert when disk usage exceeds 80%.
UptimeMag editorial team · 31 October 2026 · 4 min read

If you manage two or three VPSs, you don't need a Kubernetes cluster with Prometheus Operator to know whether a disk is about to fill up. Four binaries on a single Ubuntu 24.04 machine, one configuration file per service and an afternoon's work will do. This guide covers installing Prometheus, node_exporter, Grafana and Alertmanager on a single server, with a rule that sends email or Telegram alerts when disk space exceeds 80%.
Versions used in this guide
Versions change over time: before running the commands, always check the latest version number on the official page, because download URLs contain the version in the filename.
| Component | Version | Release date | Source |
|---|---|---|---|
| Prometheus | 3.15.1 (stable) | 21 October 2026 | prometheus.io/download |
| node_exporter | 1.12.1 | 14 July 2026 | prometheus.io/download |
| Alertmanager | 0.34.2 | 29 October 2026 | prometheus.io/download |
| Grafana OSS | 13.3.0 | 27 October 2026 | grafana.com/grafana/download |
Beyond 3.15.1, Prometheus also publishes an LTS release, 3.13.5, and a pre-release 3.16.0-rc.2: for a production server it's best to stick to the stable or LTS release, not the rc.
Step 1: node_exporter, the system probe
Create the system user and download the binary:
sudo useradd --no-create-home --shell /bin/false node_exporter
cd /tmp
wget https://github.com/prometheus/node_exporter/releases/download/v1.12.1/node_exporter-1.12.1.linux-amd64.tar.gz
tar xvfz node_exporter-1.12.1.linux-amd64.tar.gz
sudo mv node_exporter-1.12.1.linux-amd64/node_exporter /usr/local/bin/
sudo chown root:root /usr/local/bin/node_exporter
Systemd unit in /etc/systemd/system/node_exporter.service:
[Unit]
Description=Prometheus Node Exporter
After=network.target
[Service]
Type=simple
User=node_exporter
ExecStart=/usr/local/bin/node_exporter
Restart=always
[Install]
WantedBy=multi-user.target
Activation:
sudo systemctl daemon-reload
sudo systemctl enable --now node_exporter
curl -s http://localhost:9100/metrics | head
If lines starting with node_ appear, the exporter is working. Default port: 9100.
Step 2: Prometheus
cd /tmp
wget https://github.com/prometheus/prometheus/releases/download/v3.15.1/prometheus-3.15.1.linux-amd64.tar.gz
tar xvf prometheus-3.15.1.linux-amd64.tar.gz
sudo mkdir -p /etc/prometheus /var/lib/prometheus
sudo mv prometheus-3.15.1.linux-amd64/prometheus /usr/local/bin/
sudo mv prometheus-3.15.1.linux-amd64/promtool /usr/local/bin/
sudo mv prometheus-3.15.1.linux-amd64/consoles /etc/prometheus/
sudo mv prometheus-3.15.1.linux-amd64/console_libraries /etc/prometheus/
sudo useradd --no-create-home --shell /bin/false prometheus
sudo chown -R prometheus:prometheus /etc/prometheus /var/lib/prometheus
File /etc/prometheus/prometheus.yml:
global:
scrape_interval: 15s
rule_files:
- "/etc/prometheus/alert.rules.yml"
alerting:
alertmanagers:
- static_configs:
- targets: ["localhost:9093"]
scrape_configs:
- job_name: "node"
static_configs:
- targets: ["localhost:9100"]
Systemd unit in /etc/systemd/system/prometheus.service:
[Unit]
Description=Prometheus
After=network.target
[Service]
Type=simple
User=prometheus
ExecStart=/usr/local/bin/prometheus \
--config.file=/etc/prometheus/prometheus.yml \
--storage.tsdb.path=/var/lib/prometheus \
--web.listen-address=0.0.0.0:9090
Restart=always
[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now prometheus
Verify at http://VPS-IP:9090/targets: the node target should show as UP.
Step 3: the disk-over-80% rule
File /etc/prometheus/alert.rules.yml:
groups:
- name: disk
rules:
- alert: DiskAlmostFull
expr: (1 - (node_filesystem_avail_bytes{mountpoint="/",fstype!="tmpfs"} / node_filesystem_size_bytes{mountpoint="/",fstype!="tmpfs"})) * 100 > 80
for: 5m
labels:
severity: warning
annotations:
summary: "Disk over 80% on {{ $labels.instance }}"
description: "Disk usage at {{ $value | printf \"%.1f\" }}% on mountpoint /."
Reload without restarting the service:
sudo promtool check rules /etc/prometheus/alert.rules.yml
curl -X POST http://localhost:9090/-/reload
Step 4: Alertmanager and notifications
cd /tmp
wget https://github.com/prometheus/alertmanager/releases/download/v0.34.2/alertmanager-0.34.2.linux-amd64.tar.gz
tar xvf alertmanager-0.34.2.linux-amd64.tar.gz
sudo mkdir -p /etc/alertmanager /var/lib/alertmanager
sudo mv alertmanager-0.34.2.linux-amd64/alertmanager /usr/local/bin/
sudo mv alertmanager-0.34.2.linux-amd64/amtool /usr/local/bin/
sudo useradd --no-create-home --shell /bin/false alertmanager
sudo chown -R alertmanager:alertmanager /etc/alertmanager /var/lib/alertmanager
Email notifications
File /etc/alertmanager/alertmanager.yml:
global:
smtp_smarthost: 'smtp.yourmailprovider.com:587'
smtp_from: 'alert@yourdomain.com'
smtp_auth_username: 'alert@yourdomain.com'
smtp_auth_password: 'YOURPASSWORD'
route:
receiver: email-sysadmin
receivers:
- name: email-sysadmin
email_configs:
- to: 'you@yourdomain.com'
send_resolved: true
Telegram notifications
As an alternative to email, replace the receiver with:
receivers:
- name: telegram-sysadmin
telegram_configs:
- bot_token: 'YOUR_BOT_TOKEN'
chat_id: -1001234567890
send_resolved: true
parse_mode: 'HTML'
The bot is created on Telegram by talking to @BotFather; the chat_id is obtained by sending a message to the bot and reading https://api.telegram.org/bot<TOKEN>/getUpdates.
Systemd unit in /etc/systemd/system/alertmanager.service:
[Unit]
Description=Alertmanager
After=network.target
[Service]
Type=simple
User=alertmanager
ExecStart=/usr/local/bin/alertmanager \
--config.file=/etc/alertmanager/alertmanager.yml \
--storage.path=/var/lib/alertmanager
Restart=always
[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now alertmanager
Default Alertmanager port: 9093.
Step 5: Grafana and the CPU/RAM/disk/network dashboard
Grafana publishes an official APT repository; the commands below are from the official grafana.com documentation for Debian and Ubuntu:
sudo apt-get install -y apt-transport-https wget gnupg
sudo mkdir -p /etc/apt/keyrings
sudo wget -O /etc/apt/keyrings/grafana.asc https://apt.grafana.com/gpg-full.key
sudo chmod 644 /etc/apt/keyrings/grafana.asc
echo "deb [signed-by=/etc/apt/keyrings/grafana.asc] https://apt.grafana.com stable main" | sudo tee -a /etc/apt/sources.list.d/grafana.list
sudo apt-get update
sudo apt-get install grafana
sudo systemctl enable --now grafana-server
With this method, Grafana automatically updates when you run apt-get update, unlike the .deb package downloaded manually, which needs to be updated manually with every new version.
Log in at http://VPS-IP:3000 with username admin and password admin (prompted on first login). Add Prometheus as a data source pointing to http://localhost:9090. For the CPU/RAM/disk/network dashboard you don't need to build it from scratch: go to "Dashboards → Import" and import a public dashboard compatible with node_exporter metrics by entering its numeric ID from the grafana.com/dashboards catalogue and selecting the data source you just created.
Final verification: generate a test alert
To verify that the Prometheus → Alertmanager → notification chain works without waiting for the disk to actually fill up, you can send a dummy alert directly to Alertmanager using amtool:
amtool alert add alertname=DiskAlmostFull instance=test-vps severity=warning --alertmanager.url=http://localhost:9093
Within a few seconds you should receive the email or Telegram message. To see active alerts in the queue:
amtool alert query --alertmanager.url=http://localhost:9093
If the notification doesn't arrive, the first place to check is the service log: sudo journalctl -u alertmanager -f.
Security note
Ports 9090, 9093 and 9100 have no authentication by default. On an internet-facing VPS they should be filtered with ufw or iptables, allowing access only from localhost or a VPN, and exposing only Grafana's port 3000 publicly behind a reverse proxy with TLS.
Written with the help of artificial intelligence and checked by the editors (EU AI Act, art. 50).