Introduction
Proxmox VE already gives you graphs. Click into any node or VM, open the Summary tab, and you'll see CPU, memory, disk, and network usage plotted over the last hour, day, week, month, or year. For a single homelab box, that's honestly enough for most people.
It stops being enough the moment you're running two or three nodes, or you want to know what your CPU was doing at 3 a.m. last Tuesday, or you'd like a text message when a VM's disk fills up instead of finding out when it crashes. That's where Prometheus and Grafana come in. Prometheus collects and stores metrics over time, and Grafana turns those metrics into dashboards you'd actually want to look at.
There's more than one way to get Proxmox VE's numbers into Grafana. Proxmox has a built-in feature that pushes metrics straight to InfluxDB, and if you've already got an InfluxDB and Grafana stack running, that's a perfectly good option. This guide covers a different, and honestly more common, path in the wider self-hosted world: a small tool called the PVE exporter that lets Prometheus pull metrics from Proxmox VE the same way it pulls metrics from everything else in your stack. If you're already running Prometheus for other things — a NAS, a router, a handful of Docker hosts — this is the way to fold Proxmox into that same dashboard instead of running two separate monitoring systems.
Everything below was tested on Proxmox VE 9.2, which shipped in May 2026 on top of Debian 13 "Trixie." The steps also work fine on Proxmox VE 8.x — the exporter and Prometheus don't care what hypervisor version you're running underneath.
What You Will Learn
- What Prometheus, Grafana, and the PVE exporter actually do, and how they fit together
- How to create a locked-down, read-only API token in Proxmox VE just for monitoring
- How to install and run the PVE exporter as a proper systemd service
- How to point Prometheus at Proxmox VE and confirm it's actually scraping data
- How to import a ready-made Grafana dashboard so you're not building panels from scratch
- What to do when the exporter, Prometheus, or Grafana refuses to cooperate
What Is This Feature?
Three separate pieces make this work, and it helps to know what each one is before you start typing commands.
Prometheus is an open-source monitoring system that stores everything as time-series data — a metric name, a value, a timestamp, and some labels. It doesn't wait for things to send it data. Instead, on a schedule (usually every 15 or 30 seconds), it reaches out and "scrapes" a URL that returns the current numbers in a plain-text format. That pull model is the opposite of what Proxmox VE's built-in InfluxDB integration does, which pushes data out on its own.
Grafana is the visualization layer. It doesn't store any metrics itself — it connects to a data source like Prometheus and turns the stored numbers into graphs, tables, and alert rules you can actually read.
The problem is that Proxmox VE doesn't speak Prometheus's format natively. That's what the PVE exporter (the prometheus-pve-exporter project) is for. It's a small Python service that sits between the two: it calls the regular Proxmox VE API, the same one the web interface and pvesh use, and translates the response into the plain-text format Prometheus expects. You point Prometheus at the exporter, and the exporter fetches live data from Proxmox VE on every request.
Why Would You Use It?
If you only run one Proxmox VE node and nothing else, the built-in graphs will probably cover you. This setup starts paying off once your homelab grows past that.
You get one dashboard for your whole cluster instead of clicking through each node individually. Retention is whatever you configure Prometheus for — Proxmox VE's own RRD graphs cap out at one year and get less precise the further back you look, while Prometheus can keep full-resolution data for as long as your disk allows. You also get real alerting: Grafana (or Prometheus's own Alertmanager) can text, email, or push a notification to your phone when a node's memory crosses 90% instead of you noticing it three days later in the UI.
And if you're already monitoring other things with Prometheus — a pfSense box, a Synology NAS, a stack of Docker containers — adding Proxmox VE to that same system means one alerting pipeline and one place to look, instead of juggling two dashboards that don't talk to each other.
Prerequisites
Before you start, make sure you have the following:
- A working Proxmox VE 8.x or 9.x host with root or admin access, reachable over SSH
- A place to run Prometheus and Grafana — a small Debian or Ubuntu VM, or an LXC container with at least 1 vCPU and 2 GB RAM, works fine for a homelab-sized cluster
- Basic comfort typing commands over SSH — nothing advanced, but you'll be editing a couple of text files
- Network access between the machine running Prometheus and your Proxmox VE node on TCP port 9221 (the exporter) and, from the exporter's own host, to port 8006 (the Proxmox API)
You don't need Proxmox Backup Server, Ceph, or a cluster for any of this. It works the same on a single standalone node.
Step-by-Step Tutorial
Step 1: Create a read-only monitoring user and API token
Don't point the exporter at your root account. Give it its own user with just enough permission to read metrics and nothing else. In the Proxmox VE shell:
pveum user add prometheus@pve --comment "Read-only account for Prometheus"
pveum aclmod / -user prometheus@pve -role PVEAuditor
PVEAuditor is a built-in Proxmox VE role that can view configuration and status information across the whole tree, but can't change anything or start/stop a single VM. Applying it at the / path means the exporter can see every node and guest, which is what you want for a monitoring tool.
Now create an API token for that user instead of using a password:
pveum user token add prometheus@pve monitoring --privsep 0
Save the token value that command prints — it's shown exactly once and Proxmox VE won't display it again. The --privsep 0 flag disables privilege separation for this token, meaning it inherits the same PVEAuditor permissions as the user account it belongs to. Leave privilege separation on and the token would start with zero permissions of its own, and you'd have to grant the ACL a second time to the token ID specifically.
Step 2: Install the PVE exporter in a virtual environment
Run this on the Proxmox VE host itself. Debian 13 (and Debian 12 before it) blocks system-wide pip install commands by default — you'll get an error like error: externally-managed-environment if you try. A Python virtual environment sidesteps that cleanly and keeps the exporter's dependencies isolated from the system Python that Proxmox VE itself relies on.
apt update
apt install -y python3-venv
python3 -m venv /opt/prometheus-pve-exporter
/opt/prometheus-pve-exporter/bin/pip install --upgrade pip
/opt/prometheus-pve-exporter/bin/pip install prometheus-pve-exporter
That last command pulls in the exporter and its dependencies from PyPI. It only takes a few seconds on a normal connection — the package itself is small.
Step 3: Write the exporter's config file
Create the config directory and file:
mkdir -p /etc/prometheus
nano /etc/prometheus/pve.yml
Paste this in, replacing the token value with the one you saved in Step 1:
default:
user: prometheus@pve
token_name: monitoring
token_value: "your-token-value-here"
verify_ssl: false
verify_ssl: false is there because Proxmox VE's web interface uses a self-signed certificate by default. If you've replaced it with a proper certificate (through Let's Encrypt, for example), set this to true instead.
This file contains a live credential, so lock it down:
chmod 640 /etc/prometheus/pve.yml
Step 4: Run the exporter as a systemd service
You could run the exporter by hand in a terminal, but it'll die the moment you close the session or reboot the host. Give it a proper service instead. First, create a dedicated system account for it:
useradd --system --no-create-home --shell /usr/sbin/nologin prometheus
chown root:prometheus /etc/prometheus/pve.yml
Then create the unit file:
nano /etc/systemd/system/prometheus-pve-exporter.service
[Unit]
Description=Prometheus exporter for Proxmox VE
After=network.target
[Service]
Type=simple
User=prometheus
ExecStart=/opt/prometheus-pve-exporter/bin/pve_exporter --config.file /etc/prometheus/pve.yml
Restart=on-failure
[Install]
WantedBy=multi-user.target
Enable and start it:
systemctl daemon-reload
systemctl enable --now prometheus-pve-exporter
systemctl status prometheus-pve-exporter
You're looking for active (running) in the status output. If it says failed instead, skip ahead to the Troubleshooting section below.
Step 5: Test the exporter with curl
Before wiring up Prometheus, confirm the exporter is actually returning data:
curl "http://127.0.0.1:9221/pve?target=127.0.0.1&module=default"
If everything's working, you'll get back a wall of plain-text metrics that looks something like pve_up 1.0 and pve_cpu_usage_ratio{id="qemu/100"} 0.02. If you see an empty response or a connection error here, the problem is with the exporter or its config — fix it now rather than debugging Prometheus later.
Step 6: Install Prometheus and add the Proxmox scrape job
On your Prometheus VM or LXC container (a separate box from the Proxmox host is the better call here, so a busy metrics server doesn't compete with your VMs for CPU):
apt update
apt install -y prometheus
Debian ships Prometheus in its main repository, so this pulls in a working binary plus a systemd service already configured. Now edit its config:
nano /etc/prometheus/prometheus.yml
Add this under scrape_configs, replacing the IP address with your actual Proxmox VE host's address:
scrape_configs:
- job_name: 'pve'
static_configs:
- targets:
- 192.168.1.10 # your Proxmox VE host IP
metrics_path: /pve
params:
module: [default]
relabel_configs:
- source_labels: [__address__]
target_label: __param_target
- source_labels: [__param_target]
target_label: instance
- target_label: __address__
replacement: 192.168.1.10:9221 # exporter's address and port
This job definition looks more involved than a typical Prometheus target because the PVE exporter is what's called a multi-target exporter — the same exporter process can report on multiple Proxmox nodes, so you tell Prometheus which node to ask about via the target query parameter, and the relabeling rules rewrite the request so it actually reaches the exporter's port instead of trying to scrape port 9221 on the Proxmox host directly.
Restart Prometheus to pick up the change:
systemctl restart prometheus
Then open http://<prometheus-host>:9090/targets in a browser. You should see the pve job listed with a state of UP. If it says DOWN, hover over it — Prometheus shows you the exact error, which usually points straight at the problem.
Step 7: Install Grafana and connect it to Prometheus
On the same VM, or a different one if you'd rather keep them apart:
apt-get install -y apt-transport-https software-properties-common wget
wget -q -O - https://apt.grafana.com/gpg.key | gpg --dearmor | tee /usr/share/keyrings/grafana.gpg > /dev/null
echo "deb [signed-by=/usr/share/keyrings/grafana.gpg] https://apt.grafana.com stable main" | tee /etc/apt/sources.list.d/grafana.list
apt-get update
apt-get install -y grafana
systemctl enable --now grafana-server
Grafana listens on port 3000 by default. Open http://<grafana-host>:3000, log in with the default admin / admin credentials, and change the password when it asks. Then go to Connections → Data sources → Add data source, choose Prometheus, and set the URL to http://<prometheus-host>:9090. Click Save & test — you want a green confirmation before moving on.
Step 8: Import a ready-made dashboard
You don't need to build panels from nothing. The community-maintained "Proxmox via Prometheus" dashboard already covers CPU, memory, disk I/O, network throughput, and guest status per node. In Grafana, go to Dashboards → New → Import, and enter dashboard ID 10347. Select your Prometheus data source when prompted, then click Import.
Give it a minute or two — the panels need at least one full scrape interval's worth of data before the graphs stop looking empty.
Commands Explained
A quick reference for the commands used above, since it's easy to lose track of what did what:
| Command | What it does |
|---|---|
pveum user add | Creates a new Proxmox VE user account under a chosen realm (@pve means it's a local Proxmox account, not synced from LDAP or Active Directory) |
pveum aclmod | Grants a role to a user or token at a specific path in the resource tree — here, the root path, so it covers everything |
pveum user token add | Creates an API token tied to a user, which you use instead of a password for automated tools like this exporter |
python3 -m venv | Creates an isolated Python environment so packages installed into it don't touch or conflict with the system's own Python packages |
systemctl enable --now | Starts a service immediately and also configures it to start automatically on every future boot |
curl | Fetches a URL from the command line — used here to sanity-check the exporter before trusting Prometheus to talk to it |
Common Errors
error: externally-managed-environment — you tried to pip install outside a virtual environment on Debian 12 or 13. Go back to Step 2 and use the venv.
401 Client Error: Unauthorized in the exporter's logs — the token name or value in pve.yml doesn't match what Proxmox VE has on record, or --privsep 0 was left off when you created the token and it has no permissions of its own.
Get "http://192.168.1.10:9221/pve...": dial tcp: connect: connection refused on the Prometheus targets page — the exporter service isn't running, or a firewall between the two hosts is blocking port 9221. Check the Proxmox VE firewall under Datacenter → Firewall and the node-level firewall too, since Proxmox VE's firewall can block ports even when the underlying OS firewall (nftables) allows them.
Grafana panels stuck showing No data — almost always a data source mismatch. Confirm the dashboard is actually pointed at the Prometheus data source you created, not a leftover default one.
Troubleshooting
When something's not working, work through it in order rather than guessing.
Start with the exporter itself. Check its logs:
journalctl -u prometheus-pve-exporter -n 50 --no-pager
Most problems show up here immediately — a typo in the config file's YAML indentation, a missing token, or a permission error. YAML is picky about spacing, so if the service won't even start, double-check that pve.yml uses spaces consistently and not a mix of tabs and spaces.
If the exporter's logs look clean but the manual curl test from Step 5 still fails, verify the token exists and hasn't been revoked:
pveum user token list prometheus@pve
If the exporter works locally but Prometheus can't reach it, the issue is almost always network path, not configuration. Test straight from the Prometheus host:
curl "http://192.168.1.10:9221/pve?target=192.168.1.10&module=default"
If that hangs or refuses, it's a firewall or routing problem between the two machines, not anything wrong with Prometheus's YAML.
Finally, if targets are UP in Prometheus but Grafana still shows nothing, open Grafana's Explore view, pick the Prometheus data source, and manually query pve_up. If that returns a value, the data pipeline is fine and the problem is specific to the imported dashboard — try re-importing it and double-checking the data source selection during import.
Best Practices
Use an API token, not a password, for the exporter — tokens can be revoked individually without touching the user's login, which matters if this config file ever leaks.
Stick to PVEAuditor for the monitoring account. There's no reason a metrics collector needs to start, stop, or reconfigure anything, and giving it more than read access is a needless risk if that box is ever compromised.
Don't expose ports 9221, 9090, or 3000 to the public internet. Keep them on your LAN, or put a VPN like WireGuard or Tailscale in front if you need remote access to your dashboards.
Set a sane scrape interval. The default of 15 or 30 seconds is fine for a homelab — there's rarely a reason to go tighter than that, and it just means more data piling up on disk for no real benefit.
Back up your Grafana dashboards and Prometheus config alongside your other homelab configuration. It's a five-minute rebuild if you've saved the dashboard JSON and pve.yml file, and a much longer one if you haven't.
Frequently Asked Questions
Does this replace Proxmox VE's built-in graphs?
No, and it doesn't need to. The built-in graphs are still the fastest way to check a single VM's stats right now. This setup is for long-term history, cross-node dashboards, and alerting.
Can I run Prometheus and Grafana on the Proxmox host itself?
You can, but I wouldn't. A monitoring stack that lives on the same box it's monitoring goes dark exactly when you need it most — during a reboot, an update, or a hardware problem. A small separate VM or LXC container is worth the extra few minutes of setup.
Does this work with Proxmox Backup Server too?
Not with this exporter. prometheus-pve-exporter talks to the Proxmox VE API specifically. Proxmox Backup Server has its own separate API and would need a different exporter or integration.
Is any of this free?
Yes. Prometheus, Grafana, and the PVE exporter are all open source with no license or subscription involved, separate from whatever Proxmox VE subscription tier (or lack of one) you're running.
Will running the exporter slow down my Proxmox VE host?
Barely. Each scrape is a handful of read-only API calls that finish in well under a second. On a homelab-scale cluster you won't notice it in CPU or memory usage.
Conclusion
None of this is complicated once you've done it — a user, a token, a small Python service, and two config files. The part that actually takes effort is deciding what you want to be alerted about once the dashboard's live. A good next step from here is adding Node Exporter to your Proxmox host for hardware-level stats like disk temperature and SMART data, which the PVE exporter doesn't cover on its own. Once you've got Proxmox VE metrics flowing into Prometheus, it's a natural home for everything else in your homelab too.