Proxmox Monitoring with Prometheus and Grafana: Full Stack Setup

Deploy a complete observability stack for a Proxmox cluster: node_exporter on all hosts, pve-exporter for cluster metrics, Prometheus scraping everything, and Grafana dashboards with email alerting.

On this page
  1. Stack architecture
  2. Task 1: Create the monitoring container
  3. Task 2: Install node_exporter on all four Proxmox hosts
  4. Task 3: Set up pve-exporter for Proxmox API metrics
  5. Task 4: Install and configure Prometheus
  6. Task 5: Install Grafana and import dashboards
  7. Task 6: Configure alerting
  8. Monitoring is now live

A Proxmox cluster without monitoring is flying blind — you find out a disk filled up or a node overheated when something breaks, not before. This post walks through deploying a full observability stack that fixes that. The division of labor is worth understanding up front: Prometheus is the collector, quietly scraping numbers (CPU, RAM, disk, temperatures) from every node on a 15-second heartbeat and storing them as time series; Grafana is the face, turning those series into dashboards you can actually read; and Grafana’s alerting engine watches the same numbers so you get an email when something crosses a line — ideally hours before it becomes an outage.

Proxmox Monitoring: Full Prometheus + Grafana Stack — video walkthrough

Stack architecture

pvelab01node_exporter :9100pvelab02node_exporter :9100pvelab03node_exporter :9100pvelab04node_exporter :9100pve-exporterCT 105 :9221 → Proxmox APIPrometheus (CT 105)10.0.0.68:9090 · 15s scrape intervalGrafana :3000Grafana Alerting → Emailpeira.dev

All monitoring services run in a single LXC container (CT 105) on pvelab02, keeping the stack isolated and easy to back up.


Task 1: Create the monitoring container

First: make these values your own

This guide uses example values throughout: the 10.0.0.x addresses (substitute your own LAN IPs), container ID 105 and hostnames like pvelab01pvelab04 (your node names), YOUR_TOKEN_SECRET_HERE and YOUR_APP_PASSWORD (secrets you generate — keep them in a password manager, never in a note), and your@gmail.com (your alert address). If a value looks specific to one machine, it’s a placeholder to change — not a literal to copy.

1Create CT 105 on pvelab025 min
Field Value
VMID 105
Hostname monitoring
OS Debian 12 LXC (unprivileged)
Host pvelab02 (10.0.0.71)
IP 10.0.0.68
Cores 2
RAM 2048 MB
Disk 20 GB

pvelab02 is the right host: pvelab01 hosts CT 100 and CT 107; pvelab03 already has three containers. Spreading the load matters on 8 GB nodes.


Task 2: Install node_exporter on all four Proxmox hosts

node_exporter is the little agent that exposes a machine’s hardware and OS metrics for Prometheus to scrape. Run it on each Proxmox host directly — not inside containers. The reason: containers see a virtualized slice of the system, so an exporter inside one can’t read the host’s real CPU temperature, disk health, or total memory pressure. Only the bare metal has the full picture.

1Install and enable node_exporter on all four hosts5 min per host
pvelab01 / 02 / 03 / 04 — all hosts

apt-get update
apt-get install -y prometheus-node-exporter
systemctl enable --now prometheus-node-exporter

Verify it’s running and exposing metrics:

Quick verify

curl http://localhost:9100/metrics | grep node_cpu_seconds | head -5
2Verify temperature sensors are exposed2 min

The AMD Ryzen 5 2400G exposes temperature via the k10temp kernel module. Verify it’s showing up:

Check temperature metrics on any host

curl -s http://localhost:9100/metrics | grep hwmon | grep temp

You should see entries for k10temp (CPU die temp) and amdgpu (iGPU edge temp). These are the metrics that power the temperature alerts.

Note

If no hwmon metrics appear, ensure prometheus-node-exporter is version ≥ 1.5 and that the hwmon collector is not explicitly disabled. Debian Bookworm ships 1.5.0 which enables hwmon by default.


Task 3: Set up pve-exporter for Proxmox API metrics

1Create a read-only Proxmox API token for the exporter3 min

In the Proxmox web UI: Datacenter → Permissions → API Tokens → Add. Create:

Field Value
User prometheus@pve (create user first if needed)
Token ID prometheus
Privilege Separation Yes
Role PVEAuditor

Copy the token secret — you’ll only see it once.

2Install pve-exporter inside CT 1055 min
Inside CT 105

apt-get update && apt-get install -y python3-pip python3-venv
python3 -m venv /opt/pve-exporter
/opt/pve-exporter/bin/pip install prometheus-pve-exporter

# Create config
cat > /etc/pve-exporter.yml {'<<'} 'EOF'
default:
user: prometheus@pve
token_name: prometheus
token_value: YOUR_TOKEN_SECRET_HERE
verify_ssl: false
EOF

cat > /etc/systemd/system/pve-exporter.service {'<<'} 'EOF'
[Unit]
Description=Proxmox VE Prometheus Exporter
After=network.target

[Service]
ExecStart=/opt/pve-exporter/bin/pve_exporter --config.file /etc/pve-exporter.yml
Restart=always

[Install]
WantedBy=multi-user.target
EOF

systemctl enable --now pve-exporter

Task 4: Install and configure Prometheus

1Install Prometheus inside CT 1055 min
Inside CT 105

apt-get install -y prometheus
2Configure scrape targets5 min

Replace /etc/prometheus/prometheus.yml:

Edit /etc/prometheus/prometheus.yml

global:
scrape_interval: 15s
evaluation_interval: 15s

scrape_configs:
- job_name: 'node'
  static_configs:
    - targets:
        - '10.0.0.70:9100'  # pvelab01
        - '10.0.0.71:9100'  # pvelab02
        - '10.0.0.72:9100'  # pvelab03
        - '10.0.0.73:9100'  # pvelab04

- job_name: 'pve'
  static_configs:
    - targets: ['10.0.0.68:9221']
  metrics_path: /pve
  params:
    module: [default]
    cluster: ['1']
    node: ['1']
Reload Prometheus

systemctl restart prometheus
curl http://localhost:9090/-/healthy

Then open http://10.0.0.68:9090/targets and confirm every scrape job shows UP — this page is the first place to look whenever a graph goes blank:

http://10.0.0.68:9090/targets
Prometheus target health page showing node and prometheus scrape jobs, both 1/1 up with green UP state badges
Target health in the demo lab — every job green, scraping every few seconds. Your list will be longer (one node target per Proxmox host, plus pve-exporter), but it should be exactly this color.

Task 5: Install Grafana and import dashboards

1Install Grafana OSS inside CT 1055 min
Inside CT 105 — install Grafana

apt-get install -y apt-transport-https software-properties-common
wget -q -O /usr/share/keyrings/grafana.key https://apt.grafana.com/gpg.key
echo "deb [signed-by=/usr/share/keyrings/grafana.key] https://apt.grafana.com stable main" \
> /etc/apt/sources.list.d/grafana.list
apt-get update && apt-get install -y grafana
systemctl enable --now grafana-server
2Add Prometheus as a data source3 min

Open http://10.0.0.68:3000. Log in (default: admin/admin, change immediately).

Connections → Data Sources → Add → Prometheus. URL: http://localhost:9090. Click Save & Test — should show “Data source is working.”

3Import the two essential dashboards3 min

Dashboards → New → Import. Import by ID:

Dashboard ID What it shows
Node Exporter Full 1860 Per-host CPU, RAM, disk, network, temperatures
Proxmox via Prometheus 10347 VM/CT resource usage, cluster storage
http://10.0.0.68:3000/d/rYdddlPWk/node-exporter-full
Grafana Node Exporter Full dashboard showing CPU busy, RAM used, and swap gauges plus 24 hours of CPU, memory, network, and disk graphs for a Proxmox node
Node Exporter Full (ID 1860) against the demo lab's Prometheus, 24 hours in — gauges up top, per-resource history below. This is the dashboard you'll leave open on a spare monitor.

Task 6: Configure alerting

1Set up Gmail SMTP for alert delivery5 min

Edit /etc/grafana/grafana.ini inside CT 105:

Add to [smtp] section in grafana.ini

[smtp]
enabled = true
host = smtp.gmail.com:587
user = your@gmail.com
password = YOUR_APP_PASSWORD
from_address = your@gmail.com
from_name = MyOfficeLab Grafana
startTLS_policy = MandatoryStartTLS
Tip

Use a Gmail App Password (Google Account → Security → App Passwords), not your real Gmail password. App Passwords work even with 2FA enabled and can be revoked individually.

Restart Grafana after editing: systemctl restart grafana-server

2Create alert rules10 min

Alerting → Alert Rules → New Alert Rule. Create one rule per condition:

Rule PromQL Fires after
Node Down up{job="node"} == 0 2m
Container Not Running pve_up{type="lxc"} == 0 2m
Disk Usage High (1 - node_filesystem_avail_bytes/node_filesystem_size_bytes) > 0.8 5m
CPU Temp High node_hwmon_temp_celsius{chip=~".*k10temp.*",sensor="tdie"} > 80 3m
RAM Low node_memory_MemAvailable_bytes < 1073741824 5m
noDataState matters

Set noDataState: OK on all rules that use threshold-filter PromQL (like the disk and RAM rules). If the metric returns an empty result because a node is healthy, noDataState: Alerting fires a spurious alert. This is a common misconfiguration.


Monitoring is now live

You have:

  • ✅ Real-time metrics from all four Proxmox hosts
  • ✅ Cluster-level VM/CT stats via pve-exporter
  • ✅ CPU, RAM, disk, and temperature visibility
  • ✅ Email alerts for node-down, high CPU temp, low RAM, and full disks

The next post covers Tailscale: making every cluster service accessible remotely through a single subnet router, without opening any firewall ports.


Related posts:

Sources: Prometheus documentation, Grafana documentation, prometheus-pve-exporter.

Comments

Comments are powered by GitHub Discussions — sign in with a GitHub account to join the conversation.