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.

A Proxmox cluster without monitoring is flying blind. This post walks through deploying a full observability stack: Prometheus collecting metrics from all four nodes plus the Proxmox API, Grafana visualizing everything, and alerting that emails you when something goes wrong.


Stack architecture

<rect x="20" y="20" width="145" height="55" rx="8" fill="#1f2937" stroke="#27272a" stroke-width="1.5"/> <text x="92" y="40" text-anchor="middle" fill="#f4f4f5" font-size="10" font-family="monospace" font-weight="bold">pvelab01</text> <text x="92" y="56" text-anchor="middle" fill="#fbbf24" font-size="9" font-family="monospace">node_exporter :9100</text> <rect x="185" y="20" width="145" height="55" rx="8" fill="#1f2937" stroke="#27272a" stroke-width="1.5"/> <text x="257" y="40" text-anchor="middle" fill="#f4f4f5" font-size="10" font-family="monospace" font-weight="bold">pvelab02</text> <text x="257" y="56" text-anchor="middle" fill="#fbbf24" font-size="9" font-family="monospace">node_exporter :9100</text> <rect x="350" y="20" width="145" height="55" rx="8" fill="#1f2937" stroke="#27272a" stroke-width="1.5"/> <text x="422" y="40" text-anchor="middle" fill="#f4f4f5" font-size="10" font-family="monospace" font-weight="bold">pvelab03</text> <text x="422" y="56" text-anchor="middle" fill="#fbbf24" font-size="9" font-family="monospace">node_exporter :9100</text> <rect x="515" y="20" width="145" height="55" rx="8" fill="#1f2937" stroke="#27272a" stroke-width="1.5"/> <text x="587" y="40" text-anchor="middle" fill="#f4f4f5" font-size="10" font-family="monospace" font-weight="bold">pvelab04</text> <text x="587" y="56" text-anchor="middle" fill="#fbbf24" font-size="9" font-family="monospace">node_exporter :9100</text> pve-exporterCT 105 :9221 → Proxmox API<line x1="92" y1="75" x2="92" y2="165" stroke="#374151" stroke-width="1" stroke-dasharray="3,3"/><line x1="257" y1="75" x2="257" y2="165" stroke="#374151" stroke-width="1" stroke-dasharray="3,3"/><line x1="422" y1="75" x2="422" y2="165" stroke="#374151" stroke-width="1" stroke-dasharray="3,3"/><line x1="587" y1="75" x2="587" y2="165" stroke="#374151" stroke-width="1" stroke-dasharray="3,3"/>Prometheus (CT 105)10.25.144.68:9090 · 15s scrape interval<line x1="92" y1="165" x2="92" y2="175" stroke="#374151" stroke-width="1"/><line x1="257" y1="165" x2="257" y2="175" stroke="#374151" stroke-width="1"/><line x1="422" y1="165" x2="422" y2="175" stroke="#374151" stroke-width="1"/><line x1="587" y1="165" x2="587" y2="175" stroke="#374151" stroke-width="1"/>Grafana :3000Grafana Alerting → Email

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

1Create CT 105 on pvelab025 min
Field Value
VMID 105
Hostname monitoring
OS Debian 12 LXC (unprivileged)
Host pvelab02 (10.25.144.71)
IP 10.25.144.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

Run this on each Proxmox host — not in containers. node_exporter must run on the bare metal to collect hardware metrics (CPU temp, memory, disk).

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.25.144.70:9100'  # pvelab01
        - '10.25.144.71:9100'  # pvelab02
        - '10.25.144.72:9100'  # pvelab03
        - '10.25.144.73:9100'  # pvelab04

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

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

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.25.144.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.25.144.68:3000/d/rYdddlPWkCPU Usage14%CPU Temp (k10temp)52°CRAM Available9.1 GBNetwork I/O — pvelab01

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.