We’re excited to launch our first HomelabAddiction printable 🎉 Become an early supporter and get 20% off withEARLY20GET NOW

Homelab Addiction
Proxmox

How to Monitor Proxmox with Grafana and Prometheus: Build a Small Metrics Stack That Surfaces Host Pressure

Monitor Proxmox with Prometheus and Grafana by exposing node metrics, storing time series cleanly, and building dashboards that show host pressure before performance drifts.

HomelabAddiction Research Desk9 min read

FTC disclosure: This article contains affiliate links. If you purchase through these links, we may earn a commission at no additional cost to you.

Documentation basis: This rewrite is grounded in the Proxmox external metric server documentation, the Prometheus getting-started documentation, and the Grafana fundamentals documentation. The goal is to explain exporter, retention, and dashboard roles clearly before anyone starts scraping metrics.

Proxmox monitoring becomes more useful once you treat it as a history problem, not just a dashboard problem. A live web UI can show current CPU or RAM use, but it does not automatically preserve the trend lines that explain why a node keeps feeling slow during backups, replication, or noisy-neighbor bursts.

A small metrics stack solves that by separating responsibilities: exporters expose host data, Prometheus stores time-series history, and Grafana turns that history into graphs and alerts a human can review quickly.

This page helps a homelab operator choose a lightweight monitoring path for Proxmox and understand which components matter before adding dashboards, alerts, and longer-term retention.

Metrics flow diagram showing Proxmox metrics exporters feeding Prometheus and Grafana dashboards
Metrics flow: exporters expose Proxmox and host metrics, Prometheus stores them, and Grafana turns long-term history into dashboards and alerts.

Create the user and token

SSH into one Proxmox node as root and create a dedicated user:

pveum user add prometheus@pve --comment "Prometheus metrics collector"
pveum aclmod / -user prometheus@pve -role PVEAuditor
pveum user token add prometheus@pve monitoring -privsep 0

That last command prints the token value once. Save it immediately in your password manager or a local secrets file that is not committed anywhere.

Your final credential set will look like this:

  • User: prometheus@pve
  • Token name: monitoring
  • Token value: your-long-token-value

Verify the idea, not just the command

The goal here is simple:

  • the user has the PVEAuditor role
  • the permission path is /
  • the token exists and can read cluster information

If you are newer to Proxmox permissions, this is the same principle I recommend in my SSH hardening guide: give services only the access they need, then stop.

Step 2 - Create the monitoring stack files

Why this matters

Before you run containers, it helps to make the stack layout predictable. Otherwise, a week from now you will be staring at a folder called monitoring-final-v2-actual and wondering what past-you was trying to prove.

Create a working directory on your Docker host:

mkdir -p ~/proxmox-monitoring/prometheus
mkdir -p ~/proxmox-monitoring/grafana/provisioning/datasources
mkdir -p ~/proxmox-monitoring/pve
cd ~/proxmox-monitoring

Now create this docker-compose.yml file:

services:
  pve-exporter:
    image: prompve/prometheus-pve-exporter:latest
    container_name: pve-exporter
    restart: unless-stopped
    ports:
      - "127.0.0.1:9221:9221"
    volumes:
      - ./pve/pve.yml:/etc/prometheus/pve.yml:ro

  prometheus:
    image: prom/prometheus:latest
    container_name: prometheus
    restart: unless-stopped
    command:
      - --config.file=/etc/prometheus/prometheus.yml
      - --storage.tsdb.path=/prometheus
      - --web.enable-lifecycle
    ports:
      - "9090:9090"
    volumes:
      - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro
      - prometheus_data:/prometheus
    depends_on:
      - pve-exporter

  grafana:
    image: grafana/grafana-oss:latest
    container_name: grafana
    restart: unless-stopped
    ports:
      - "3000:3000"
    volumes:
      - grafana_data:/var/lib/grafana
      - ./grafana/provisioning/datasources:/etc/grafana/provisioning/datasources:ro
    depends_on:
      - prometheus

volumes:
  prometheus_data:
  grafana_data:

A couple of notes:

  • I bind the exporter to 127.0.0.1:9221 so it is not exposed to the whole LAN by default.
  • Prometheus and Grafana can be exposed to your LAN, but I still recommend putting them behind a reverse proxy later if you want remote access. If you need that piece, my Nginx Proxy Manager guide walks through the basics.

Step 3 - Configure the Proxmox exporter

Why this matters

The exporter is the translator between Prometheus and Proxmox. Prometheus cannot scrape the Proxmox API directly in the format Grafana expects for these dashboards. The exporter handles that translation for you.

Create ~/proxmox-monitoring/pve/pve.yml:

default:
  user: prometheus@pve
  token_name: monitoring
  token_value: YOUR_LONG_TOKEN_VALUE
  verify_ssl: false

Replace YOUR_LONG_TOKEN_VALUE with the actual token value from Step 1.

What could go wrong

If your Proxmox node uses a self-signed certificate, verify_ssl: false is usually the quickest beginner path. Long term, I prefer trusted certificates, but there is no point pretending every homelab starts there.

If you already use valid certificates, set this instead:

verify_ssl: true

Step 4 - Configure Prometheus scraping

Why this matters

Prometheus needs to know where metrics live, how often to collect them, and how to label them. This is where a lot of "Grafana shows nothing" problems actually start.

Create ~/proxmox-monitoring/prometheus/prometheus.yml:

global:
  scrape_interval: 30s
  evaluation_interval: 30s

scrape_configs:
  - job_name: prometheus
    static_configs:
      - targets:
          - localhost:9090

  - job_name: pve
    static_configs:
      - targets:
          - 192.168.1.10
    metrics_path: /pve
    params:
      module: [default]
      cluster: ['1']
      node: ['1']
    relabel_configs:
      - source_labels: [__address__]
        target_label: __param_target
      - source_labels: [__param_target]
        target_label: instance
      - target_label: __address__
        replacement: pve-exporter:9221

Replace 192.168.1.10 with the IP or hostname of your Proxmox node.

If you have a cluster, list each Proxmox node under targets:

      - targets:
          - 192.168.1.10
          - 192.168.1.11
          - 192.168.1.12

This configuration follows the exporter project pattern where Prometheus sends the Proxmox node as the target parameter and the exporter does the API call on its behalf.

Step 5 - Provision the Grafana datasource

Why this matters

You can add the datasource by hand in the UI. I still prefer provisioning it in a file because it makes rebuilds painless. If you redeploy the stack later, Grafana comes back with the datasource ready instead of making you click through the same screens again.

Create ~/proxmox-monitoring/grafana/provisioning/datasources/prometheus.yml:

apiVersion: 1

datasources:
  - name: Prometheus
    type: prometheus
    access: proxy
    url: http://prometheus:9090
    isDefault: true
    editable: true

Step 6 - Start the stack

Why this matters

Now we move from configuration to proof. The point is not just to start containers. The point is to confirm each layer works before you pile the next layer on top.

Launch the stack:

cd ~/proxmox-monitoring
docker compose up -d

Check container status:

docker compose ps

Then follow the logs if something looks off:

docker compose logs -f pve-exporter
docker compose logs -f prometheus
docker compose logs -f grafana

Verify the exporter first

From the Docker host, test the exporter endpoint:

curl -s http://127.0.0.1:9221/metrics | head

You should see exporter self-metrics. Then test the Proxmox-backed endpoint through Prometheus later, but this quick check confirms the container is alive.

Verify Prometheus targets

Open Prometheus at http://YOUR-DOCKER-HOST:9090/targets.

You want to see the pve target as UP. If it is DOWN, do not move to Grafana yet. Fix the scrape first.

This step saves time because Grafana is just a mirror here. If Prometheus has nothing, Grafana will faithfully visualize nothing (which is a very honest failure mode, to be fair).

Step 7 - Import the Proxmox Grafana dashboard

Why this matters

Grafana without a dashboard is just a very polished blank page. The easiest starting point is the public Proxmox dashboard with ID 10347 from Grafana Labs.

Open Grafana at http://YOUR-DOCKER-HOST:3000.

Default login:

  • Username: admin
  • Password: admin

Grafana will ask you to change the password on first login.

Then import dashboard ID 10347:

  1. Go to Dashboards
  2. Click New -> Import
  3. Enter 10347
  4. Choose the Prometheus datasource
  5. Finish the import

That dashboard is a good beginner starting point because it covers:

  • node CPU and memory
  • storage allocation and usage
  • guest CPU, memory, disk IO, and network IO
  • historical trends that the Proxmox UI does not keep front and center

Step 8 - Check that the graphs mean something

Why this matters

A dashboard full of moving lines feels impressive, but the real question is whether it helps you catch useful problems. I like to do a quick sanity test right after setup.

Try one or two controlled changes:

# On a busy VM or container host
apt update

# Or generate a little disk activity on a test VM
fio --name=randread --rw=randread --bs=4k --size=256M --runtime=60 --time_based

Then watch whether:

  • CPU changes on the correct node
  • IO appears where you expect
  • guest graphs line up with your test workload

If the graphs move in the wrong place, you probably have a target or instance-label mismatch.

Common mistakes

1. Using an admin account instead of a monitoring account

You do not need full admin rights for metrics. Use PVEAuditor and keep it boring.

2. Importing the dashboard before Prometheus targets are up

This wastes time. Always verify http://YOUR-HOST:9090/targets first.

3. Forgetting to replace the Proxmox target IP in prometheus.yml

I have seen people copy a config, stand up the stack, and then wonder why Grafana is still blank. It turns out they were scraping somebody else's example IP. Computers are ruthless about details.

4. Leaving the exporter open on all interfaces

Bind it to 127.0.0.1 unless you have a good reason not to. Metrics endpoints should not be casually exposed.

5. Blaming Grafana for a Prometheus problem

If Prometheus does not scrape the exporter successfully, Grafana cannot rescue the situation. Check the stack in this order:

  1. Proxmox token and permissions
  2. exporter container logs
  3. Prometheus target status
  4. Grafana datasource
  5. imported dashboard settings

Troubleshooting checklist

If the dashboard shows no data, walk through these checks in order:

# Is the exporter container running?
docker compose ps

# Does the exporter respond locally?
curl -s http://127.0.0.1:9221/metrics | head

# Can Prometheus see the config you expect?
docker compose exec prometheus cat /etc/prometheus/prometheus.yml

# Reload Prometheus after config changes if needed
curl -X POST http://localhost:9090/-/reload

Then confirm in the UI:

  • Prometheus -> Status -> Targets -> pve is UP
  • Grafana -> Connections -> Data sources -> Prometheus is healthy
  • Imported dashboard is using the same datasource you configured

If you want a broader monitoring foundation after this, my article on homelab monitoring with Prometheus and Grafana expands the stack beyond Proxmox specifically. And if you are building Docker-based services next, these Docker Compose best practices will save you some future cleanup.

FAQ

Do I need to run Grafana and Prometheus on the Proxmox host itself?

No. In most homelabs, I prefer a separate VM or Docker host. It keeps the monitoring stack isolated and makes maintenance easier.

Can I use a password instead of an API token?

Yes, but I do not recommend it if token auth is available. Tokens are cleaner, easier to rotate, and generally a better habit.

Should I monitor every Proxmox node separately?

Yes. If you have multiple nodes, add each node as a target in Prometheus so you get complete node-level visibility.

What if my Proxmox node uses a self-signed certificate?

Use verify_ssl: false in pve.yml as the short-term fix. Long term, trusted certificates are the better answer.

Is this enough for alerting too?

It is enough to get started with visibility. Once the data is flowing, you can add Prometheus alert rules or an Alertmanager stack later.

What to learn next

Once you have Proxmox metrics working, the next useful improvements are:

  • add node exporter on important Linux VMs for OS-level metrics
  • monitor your backup jobs and storage pool health
  • put Grafana behind HTTPS and SSO
  • build a few simple alerts for disk exhaustion, host memory pressure, and guest downtime

For official references, keep these handy:

If you came into this feeling overwhelmed by the terminology, that is normal. The trick is to stop treating monitoring like one giant product and start treating it like a small chain of jobs. Proxmox exposes data. The exporter translates it. Prometheus stores it. Grafana shows it. Once that clicks, the setup becomes much easier to manage.

And once you have historical graphs for your cluster, a whole category of weird homelab problems stops feeling mysterious. Which is nice, because the homelab will always invent new ways to be weird.

transmission signupstatus: open channel

article_topic // How to Monitor Proxmox with Grafana and Prometheus

Start building a smarter homelab.

Join readers learning Proxmox, networking, storage, backups, and self-hosting without breaking everything.

Enter your email address to receive the Homelab Addiction newsletter.

Beginner-friendly

No gatekeeping. Just clear, actionable guides.

1 useful email / week

Practical tips, real-world setups, and lessons learned.

Zero hype, practical only

What works, what breaks, and how to fix it.

Reply to any email with what you're building.
I read and reply to as many as I can.

— The Homelab Addiction Operator

support // the lab

Found this guide useful?

If it saved you time or a rebuild, you can support more practical homelab guides.

Support the lab