Your WireGuard tunnel runs. But you wonder: how many Mbps does it pull at peak? Which peers connect when? Does the Contabo VPS actually hit its advertised 200 Mbps? You could open a terminal and tail -f journalctl every time, or install a Prometheus + Grafana stack in 30 minutes that gives you visual answers, alerts on incidents, and keeps history.
This guide implements the setup we run ourselves on Contabo VPS S for 14 months. node_exporter for the system, wireguard_exporter for peers, Prometheus for storage, Grafana for visualization, Alertmanager + Discord webhook for alerts.
Architecture
Everything runs on the same Contabo VPS. Three systemd processes:
- node_exporter (port 9100): system metrics (CPU, RAM, disk, network)
- wireguard_exporter (port 9586): WireGuard peer metrics (handshake, bytes_in/out, last_seen)
- prometheus (port 9090): scrapes exporters every 15s, retains 30 days
- grafana (port 3000): dashboards
- alertmanager (port 9093): alert rules + Discord webhook
Cost: ~250 MB RAM, ~3% CPU steady on a 4 vCPU VPS. No perceptible impact on the tunnel.
Step 1 — Install node_exporter
# On the VPS, with sudo
sudo useradd --no-create-home --shell /usr/sbin/nologin node_exporter
cd /tmp
wget https://github.com/prometheus/node_exporter/releases/download/v1.8.2/node_exporter-1.8.2.linux-amd64.tar.gz
tar xzf node_exporter-1.8.2.linux-amd64.tar.gz
sudo cp node_exporter-1.8.2.linux-amd64/node_exporter /usr/local/bin/
sudo chown node_exporter:node_exporter /usr/local/bin/node_exporter
systemd unit /etc/systemd/system/node_exporter.service:
[Unit]
Description=Node Exporter
After=network.target
[Service]
User=node_exporter
Group=node_exporter
Type=simple
ExecStart=/usr/local/bin/node_exporter --web.listen-address=127.0.0.1:9100
[Install]
WantedBy=multi-user.target
Important: we bind on 127.0.0.1 (not 0.0.0.0) — no public exposure. Prometheus accesses via localhost.
sudo systemctl daemon-reload
sudo systemctl enable --now node_exporter
curl -s http://127.0.0.1:9100/metrics | head -20
# Should return metrics like "node_cpu_seconds_total"
Step 2 — Install prometheus-wireguard-exporter
The exporter we use: github.com/MindFlavor/prometheus_wireguard_exporter (Rust, ~3 MB binary, scrapes wg show output).
cd /tmp
wget https://github.com/MindFlavor/prometheus_wireguard_exporter/releases/download/3.6.6/prometheus_wireguard_exporter_3.6.6_linux_amd64.tar.gz
tar xzf prometheus_wireguard_exporter_3.6.6_linux_amd64.tar.gz
sudo cp prometheus_wireguard_exporter /usr/local/bin/
sudo chmod +x /usr/local/bin/prometheus_wireguard_exporter
systemd unit /etc/systemd/system/wireguard_exporter.service:
[Unit]
Description=WireGuard Prometheus Exporter
After=network.target wg-quick@wg0.service
Requires=wg-quick@wg0.service
[Service]
User=root
ExecStart=/usr/local/bin/prometheus_wireguard_exporter -a 127.0.0.1 -p 9586 -n /etc/wireguard/wg0.conf
Restart=on-failure
[Install]
WantedBy=multi-user.target
The -n /etc/wireguard/wg0.conf flag lets the exporter use peer names (commented in the .conf) as Prometheus labels. More readable in Grafana than bare pubkeys.
sudo systemctl daemon-reload
sudo systemctl enable --now wireguard_exporter
curl -s http://127.0.0.1:9586/metrics | grep wireguard_
# Should return wireguard_sent_bytes_total, wireguard_received_bytes_total, etc.
Step 3 — Install Prometheus
sudo useradd --no-create-home --shell /usr/sbin/nologin prometheus
sudo mkdir -p /etc/prometheus /var/lib/prometheus
sudo chown prometheus:prometheus /etc/prometheus /var/lib/prometheus
cd /tmp
wget https://github.com/prometheus/prometheus/releases/download/v2.55.1/prometheus-2.55.1.linux-amd64.tar.gz
tar xzf prometheus-2.55.1.linux-amd64.tar.gz
sudo cp prometheus-2.55.1.linux-amd64/prometheus /usr/local/bin/
sudo cp prometheus-2.55.1.linux-amd64/promtool /usr/local/bin/
sudo chown prometheus:prometheus /usr/local/bin/prometheus /usr/local/bin/promtool
Config /etc/prometheus/prometheus.yml:
global:
scrape_interval: 15s
evaluation_interval: 15s
alerting:
alertmanagers:
- static_configs:
- targets:
- 127.0.0.1:9093
rule_files:
- "alert_rules.yml"
scrape_configs:
- job_name: prometheus
static_configs:
- targets: [127.0.0.1:9090]
- job_name: node
static_configs:
- targets: [127.0.0.1:9100]
labels:
instance: vps-contabo-nuremberg
- job_name: wireguard
static_configs:
- targets: [127.0.0.1:9586]
labels:
instance: vps-contabo-nuremberg
Alert rules /etc/prometheus/alert_rules.yml:
groups:
- name: vpn_alerts
interval: 30s
rules:
- alert: HighCPU
expr: 100 - (avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 80
for: 5m
annotations:
summary: "CPU > 80% on {{ $labels.instance }}"
- alert: WireGuardPeerDown
expr: time() - wireguard_latest_handshake_seconds > 600
for: 5m
annotations:
summary: "WG peer {{ $labels.peer }} hasn't handshaken in >10min"
- alert: HighBandwidth
expr: rate(node_network_transmit_bytes_total{device="eth0"}[5m]) * 8 > 180000000
for: 10m
annotations:
summary: "Bandwidth > 180 Mbps on eth0 — approaching Contabo limit"
- alert: DiskAlmostFull
expr: (node_filesystem_avail_bytes{mountpoint="/"} / node_filesystem_size_bytes{mountpoint="/"}) * 100 < 15
for: 10m
annotations:
summary: "Disk < 15% free on {{ $labels.instance }}"
systemd unit /etc/systemd/system/prometheus.service:
[Unit]
Description=Prometheus
After=network.target
[Service]
User=prometheus
Group=prometheus
Type=simple
ExecStart=/usr/local/bin/prometheus \
--config.file /etc/prometheus/prometheus.yml \
--storage.tsdb.path /var/lib/prometheus/ \
--storage.tsdb.retention.time=30d \
--web.listen-address=127.0.0.1:9090
[Install]
WantedBy=multi-user.target
sudo chown prometheus:prometheus /etc/prometheus/prometheus.yml /etc/prometheus/alert_rules.yml
sudo systemctl daemon-reload
sudo systemctl enable --now prometheus
sudo systemctl status prometheus
Step 4 — Install Grafana
sudo apt install -y apt-transport-https software-properties-common
sudo mkdir -p /etc/apt/keyrings/
wget -q -O - https://apt.grafana.com/gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/grafana.gpg
echo "deb [signed-by=/etc/apt/keyrings/grafana.gpg] https://apt.grafana.com stable main" | sudo tee /etc/apt/sources.list.d/grafana.list
sudo apt update
sudo apt install -y grafana
Edit /etc/grafana/grafana.ini to bind localhost only:
[server]
http_addr = 127.0.0.1
http_port = 3000
sudo systemctl enable --now grafana-server
Step 5 — Access Grafana securely
Instead of opening port 3000 publicly, SSH tunnel from your laptop:
ssh -L 3000:127.0.0.1:3000 eric@vpn.example.com
Then open http://127.0.0.1:3000 in your local browser. Login admin / admin (change immediately).
Alternative: access via the WireGuard tunnel. If your VPS is 10.66.66.1 on the VPN side, hit http://10.66.66.1:3000 from any connected peer. No port exposed to public Internet.
Step 6 — Configure the Prometheus datasource
In Grafana:
- Settings → Data Sources → Add data source → Prometheus
- URL:
http://127.0.0.1:9090 - Save & Test → "Data source is working"
Step 7 — Import dashboards
Ready-made dashboards:
- Node Exporter Full: ID
1860on grafana.com — full system metrics - WireGuard: ID
12557or newer — peers, bandwidth, last_seen
In Grafana: Dashboards → Import → paste the ID → pick the Prometheus datasource → Import.
For a custom VPS-VPN dashboard, we published our JSON on GitHub (link in the VPNSmith repo). It contains:
- Overview: RAM/CPU/disk/uptime
- eth0 bandwidth (in/out, 1h/24h/7d)
- WireGuard peers: name, last handshake, exchanged bytes
- Top peers by usage
- Active alerts
Step 8 — Discord alerts via webhook
Install Alertmanager:
cd /tmp
wget https://github.com/prometheus/alertmanager/releases/download/v0.27.0/alertmanager-0.27.0.linux-amd64.tar.gz
tar xzf alertmanager-0.27.0.linux-amd64.tar.gz
sudo cp alertmanager-0.27.0.linux-amd64/alertmanager /usr/local/bin/
sudo cp alertmanager-0.27.0.linux-amd64/amtool /usr/local/bin/
sudo useradd --no-create-home --shell /usr/sbin/nologin alertmanager
sudo mkdir -p /etc/alertmanager /var/lib/alertmanager
sudo chown alertmanager:alertmanager /etc/alertmanager /var/lib/alertmanager
Config /etc/alertmanager/alertmanager.yml:
global:
resolve_timeout: 5m
route:
receiver: discord
receivers:
- name: discord
webhook_configs:
- url: 'https://discord.com/api/webhooks/XXXXXXX/YYYYYYY?wait=true'
send_resolved: true
Generate the Discord webhook: Server Settings → Integrations → Webhooks → New Webhook. Copy URL. Raw Prometheus format isn't pretty in Discord — we use the bridge alertmanager-discord or a simple parser that produces clean Discord embeds.
systemd unit /etc/systemd/system/alertmanager.service:
[Unit]
Description=Alertmanager
After=network.target
[Service]
User=alertmanager
Group=alertmanager
Type=simple
ExecStart=/usr/local/bin/alertmanager \
--config.file=/etc/alertmanager/alertmanager.yml \
--storage.path=/var/lib/alertmanager \
--web.listen-address=127.0.0.1:9093
[Install]
WantedBy=multi-user.target
sudo chown alertmanager:alertmanager /etc/alertmanager/alertmanager.yml
sudo systemctl daemon-reload
sudo systemctl enable --now alertmanager
Useful queries for a VPN VPS
In Grafana, these Prometheus queries are the most useful day-to-day:
Total WireGuard bandwidth (Mbps):
rate(wireguard_sent_bytes_total[5m]) * 8 / 1e6 + rate(wireguard_received_bytes_total[5m]) * 8 / 1e6
Top 5 peers by received bytes (24h):
topk(5, increase(wireguard_received_bytes_total[24h]))
Last handshake per peer (humanized):
time() - wireguard_latest_handshake_seconds
CPU utilization %:
100 - (avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)
Disk usage %:
100 - (node_filesystem_avail_bytes{mountpoint="/"} / node_filesystem_size_bytes{mountpoint="/"}) * 100
Backup and retention
Prometheus keeps 30 days by default (--storage.tsdb.retention.time=30d). Beyond, either raise retention (add disk) or downsample with Thanos or VictoriaMetrics (overkill for a personal VPS).
/var/lib/prometheus/ burns ~50-100 MB per day for this stack. At 30 days, ~2-3 GB max. Your Contabo VPS has 50 GB NVMe, plenty of margin.
Additional hardening
- fail2ban on Grafana: build a filter for login attempts. 5× failed auth → 15-min ban.
- Reverse proxy with basic auth: Caddy or Nginx in front of Grafana if you want to share the dashboard with a colleague without SSH tunnel. But always prefer the WireGuard tunnel if possible.
- TLS: if you expose Grafana outside LAN/tunnel, certbot + Caddy in 5 minutes.
Verdict
With this stack you get real-time visibility into everything passing on your VPS VPN. You immediately see when a peer pulls 200 GB overnight (probably a Linux ISO, or Plex backup), when CPU spikes (often a misconfigured OOM kill), when a peer hasn't handshaken in 1h (dead or gone client).
It's 30 minutes of setup once, and 14 months of peace with a Contabo VPS S holding its 200 Mbps without surprise.
To get started:
- Contabo VPS + WireGuard setup in 20 min
- Ready-to-use WireGuard templates
- Netfilter kill switch to never leak
The VPS we use: /go/contabo — 4.99 €/mo, 200 Mbps, German GDPR jurisdiction. Our full Contabo VPS review after 14 months of continuous use.
★ Datacenter Nuremberg GDPR · ✓ IPv4 dédiée incluse · 200+ Mbps garantis
Get Contabo30 jours satisfait ou remboursé→