How to Set Up Prometheus and Grafana for VPS Monitoring

Prometheus collects and stores time-series metrics; Grafana turns them into dashboards and alerts. Together they are the standard way to answer "what was this server doing at 3am" without guessing. This guide installs both on a single Ubuntu or Debian VPS, wires up host metrics, secures the two web interfaces behind Nginx, and sets a retention policy that will not fill your disk.

Prerequisites

  • Ubuntu 22.04/24.04 or Debian 11/12 VPS
  • Root or sudo access
  • At least 2 GB RAM. Prometheus is memory-hungry in proportion to the number of active series it holds; 1 GB works for one host but leaves no headroom once you add targets.
  • A domain name pointed at the server if you want HTTPS on the dashboards

How the Pieces Fit Together

Three separate programs, and mixing them up is the most common source of confusion:

  • node_exporter reads CPU, memory, disk and network from the kernel and publishes them as plain text on port 9100. It stores nothing.
  • Prometheus scrapes that endpoint on a timer and stores the results in its own database on port 9090.
  • Grafana queries Prometheus and draws the graphs on port 3000. It stores no metrics of its own.

Metrics flow one way: node_exporter → Prometheus → Grafana. Prometheus pulls; nothing is pushed to it.

Step 1 — Create Service Users

Neither daemon should run as root, and neither needs a login shell or a home directory:

sudo useradd --no-create-home --shell /usr/sbin/nologin prometheus
sudo useradd --no-create-home --shell /usr/sbin/nologin node_exporter
sudo mkdir -p /etc/prometheus /var/lib/prometheus
sudo chown prometheus:prometheus /var/lib/prometheus

Step 2 — Install node_exporter

Check the current release version on the Prometheus download page, then adjust the version below:

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

Use linux-arm64 instead if uname -m reports aarch64.

sudo nano /etc/systemd/system/node_exporter.service
[Unit]
Description=Prometheus Node Exporter
After=network-online.target

[Service]
User=node_exporter
Group=node_exporter
Type=simple
ExecStart=/usr/local/bin/node_exporter --web.listen-address=127.0.0.1:9100
Restart=always

[Install]
WantedBy=multi-user.target

Binding to 127.0.0.1 rather than all interfaces matters: node_exporter has no authentication of any kind, and on a public address it hands your entire system profile to anyone who asks. Keep it on loopback while Prometheus runs on the same host.

sudo systemctl daemon-reload
sudo systemctl enable --now node_exporter
curl -s localhost:9100/metrics | head

Step 3 — Install Prometheus

cd /tmp
wget https://github.com/prometheus/prometheus/releases/download/v2.53.0/prometheus-2.53.0.linux-amd64.tar.gz
tar xzf prometheus-2.53.0.linux-amd64.tar.gz
cd prometheus-2.53.0.linux-amd64
sudo cp prometheus promtool /usr/local/bin/
sudo cp -r consoles console_libraries /etc/prometheus/
sudo chown -R prometheus:prometheus /etc/prometheus /usr/local/bin/prometheus

Step 4 — Configure Scraping

sudo nano /etc/prometheus/prometheus.yml
global:
  scrape_interval: 15s
  evaluation_interval: 15s

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: 'web-01'

Set instance to something you will recognise. Once you monitor a second server, "127.0.0.1:9100" on every dashboard is useless.

Validate before restarting — Prometheus refuses to start on a bad config, and a typo at 2am is a silent outage of your monitoring:

promtool check config /etc/prometheus/prometheus.yml

Step 5 — Run Prometheus with a Retention Limit

sudo nano /etc/systemd/system/prometheus.service
[Unit]
Description=Prometheus
After=network-online.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 \
  --storage.tsdb.retention.size=5GB \
  --web.listen-address=127.0.0.1:9090
Restart=always

[Install]
WantedBy=multi-user.target

Both retention flags are here on purpose. Time-based retention alone will still fill a small disk if you add targets, and a full disk stops Prometheus writing and takes your monitoring down at exactly the moment you need it. Whichever limit is reached first wins.

sudo systemctl daemon-reload
sudo systemctl enable --now prometheus
sudo systemctl status prometheus

Step 6 — 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
sudo systemctl enable --now grafana-server

Grafana listens on port 3000 and ships with the login admin/admin. Change it on first sign-in, before the instance is reachable from outside.

Step 7 — Put Both Behind Nginx and Close the Ports

Prometheus has no login screen at all. Anyone who can reach port 9090 can read every metric and query the whole database, so it should never be open to the internet. Reach it through a reverse proxy with authentication, or over an SSH tunnel.

sudo apt install -y nginx apache2-utils
sudo htpasswd -c /etc/nginx/.htpasswd monitor
server {
    listen 80;
    server_name monitor.example.com;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    location /prometheus/ {
        auth_basic "Metrics";
        auth_basic_user_file /etc/nginx/.htpasswd;
        proxy_pass http://127.0.0.1:9090/;
    }
}
sudo nginx -t && sudo systemctl reload nginx
sudo certbot --nginx -d monitor.example.com

If you would rather expose nothing at all, skip the proxy and tunnel instead:

ssh -L 3000:127.0.0.1:3000 -L 9090:127.0.0.1:9090 user@your-server

Step 8 — Connect Grafana to Prometheus

In Grafana, go to Connections → Data sources → Add data source → Prometheus, and set the URL to http://127.0.0.1:9090. Save & test should report success. Then Dashboards → New → Import and enter dashboard ID 1860 (Node Exporter Full), selecting your Prometheus data source when prompted. That gives you CPU, memory, disk, filesystem and network panels without building anything by hand.

Queries Worth Knowing

Grafana dashboards are PromQL underneath. These four cover most of what you will actually be asked:

# CPU used, as a percentage
100 - (avg by(instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)

# Memory used, as a percentage
100 * (1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)

# Root filesystem free, as a percentage
100 * node_filesystem_avail_bytes{mountpoint="/"} / node_filesystem_size_bytes{mountpoint="/"}

# Is the target up? 1 means yes
up{job="node"}

rate() is required on counters such as node_cpu_seconds_total, which only ever increase. Graphing the raw counter draws a line going up and to the right forever and tells you nothing.

Adding a Second Server

On the new host, install node_exporter as above, but bind it to the private network address rather than loopback so the Prometheus server can reach it, and allow only that source:

sudo ufw allow from 10.0.0.5 to any port 9100 proto tcp

Then add it to prometheus.yml under the same job and reload:

sudo systemctl reload prometheus

Reload re-reads the config without dropping the stored data. A restart is not needed for a config change.

Common Errors

Grafana shows "No data" but the data source tests fine — check the time range in the top-right corner first; a range that predates the install is empty by definition. Then confirm the target is actually being scraped at Status → Targets in the Prometheus UI.

Target shows DOWN with "connection refused" — node_exporter is not running, or it is bound to 127.0.0.1 on a different machine than Prometheus. curl the exporter's /metrics endpoint from the Prometheus host to confirm which.

Prometheus will not start after a config edit — run promtool check config, then journalctl -u prometheus -n 50. YAML indentation is nearly always the cause.

Disk filling up — both retention flags must be present, and they only apply to new data after a restart. Check current usage with du -sh /var/lib/prometheus/.

Grafana behind a proxy shows a blank page or broken assets — set root_url in /etc/grafana/grafana.ini to the public URL and restart grafana-server.

Best Practices

  • Keep Prometheus and node_exporter on loopback and reach them through a proxy or SSH tunnel — neither has authentication of its own.
  • Set both retention flags on day one, not after the disk fills.
  • Give every target a meaningful instance label before you have more than one.
  • Use systemctl reload for config changes and keep the stored history.
  • Alert on up == 0 as well as on thresholds — a target that stopped reporting looks identical to a healthy one on a graph.

FAQ

How much disk will this use?
Roughly 1—2 GB per month for a single host at a 15-second scrape interval with the default node_exporter collectors. It scales with the number of series, so a second server roughly doubles it.

Can I run this on a 1 GB VPS?
For one monitored host, yes. Grafana and Prometheus together idle at around 300—400 MB, which leaves little room for anything else on the box — and none for growth.

Do I need Alertmanager?
Not to start. Grafana can send alerts on its own, which is enough for a handful of servers. Alertmanager earns its place when you need routing, grouping and silences.

Continue Reading

Browse more articles in Performance & Monitoring.

  • prometheus, grafana, vps monitoring, node exporter
  • 0 Users Found This Useful
Was this answer helpful?

Related Articles

How to Install Netdata for Real-Time VPS Monitoring

Netdata provides a real-time, highly detailed web dashboard showing CPU, memory, disk, network,...

How to Set Up Uptime Monitoring for Your Website

Uptime monitoring alerts you the moment your website or application goes down — ideally...

How to Set Up Centralized Logging Across Multiple VPS Instances

When running multiple servers, checking logs individually on each one is slow and error-prone...

How to Profile and Optimize Slow Application Requests

When a server has plenty of free CPU and RAM but specific requests are still slow, the bottleneck...

How to Plan VPS Capacity for Future Growth

Reacting to performance problems after they occur is stressful and risky. This guide covers...