If you’ve been pricing out SaaS monitoring platforms and wincing at the per-host cost, you’re not alone. Prometheus and Grafana are the combination most self-hosted infrastructure teams reach for instead — a free, open-source stack that gives you real-time metrics, dashboards, and alerting without sending a single byte of data to a third party. This guide walks through a complete server monitoring setup on plain Linux VMs and bare-metal boxes — no Kubernetes required — so you can go from a blank server to a working dashboard in under an hour.
What Prometheus and Grafana actually do
It helps to be honest about the division of labor before you install anything, because a lot of guides blur it.
- Prometheus is a time-series database and scraper. It periodically pulls metrics (CPU, memory, disk, network, custom app stats) from targets over HTTP, stores them locally, and lets you query them with its own language, PromQL. It does not draw graphs for humans — that’s not its job.
- Grafana is the visualization and dashboarding layer. It doesn’t collect or store metrics itself; it connects to a data source — in this case Prometheus — and turns the raw numbers into graphs, gauges, and alert-worthy panels.
- node_exporter is the piece that actually exposes Linux system metrics in a format Prometheus understands. Without it, Prometheus has nothing useful to scrape from a plain server.
Together they form a pull-based pipeline: node_exporter exposes metrics, Prometheus scrapes and stores them, and Grafana queries Prometheus and renders dashboards. Nothing phones home, and everything lives on infrastructure you control.
What you’ll need
- One Linux server to run Prometheus and Grafana (a 2 vCPU / 2-4GB RAM VM is plenty for monitoring up to a few dozen hosts).
- SSH and root or sudo access on every server you want to monitor.
- Basic comfort editing a config file and using systemd (
systemctl). - Open ports between the Prometheus server and each monitored host: 9100 (node_exporter) inbound on the monitored servers, and 9090 (Prometheus) and 3000 (Grafana) reachable from wherever you’ll access the UI.
- About 30-60 minutes.
This guide assumes Ubuntu/Debian-style servers using apt, but the Prometheus and node_exporter steps are near-identical on any systemd-based distro — just swap the package manager commands.
Step-by-step setup
1. Install Prometheus
Run this on the dedicated monitoring server. Create a system user, pull the latest release, and drop the binaries in place:
sudo useradd --no-create-home --shell /usr/sbin/nologin prometheus
sudo mkdir -p /etc/prometheus /var/lib/prometheus
cd /tmp
wget https://github.com/prometheus/prometheus/releases/download/v3.0.1/prometheus-3.0.1.linux-amd64.tar.gz
tar xvf prometheus-3.0.1.linux-amd64.tar.gz
cd prometheus-3.0.1.linux-amd64
sudo cp prometheus promtool /usr/local/bin/
sudo cp -r consoles console_libraries /etc/prometheus/
sudo cp prometheus.yml /etc/prometheus/prometheus.yml
sudo chown -R prometheus:prometheus /etc/prometheus /var/lib/prometheus
sudo chown prometheus:prometheus /usr/local/bin/prometheus /usr/local/bin/promtoolCreate a systemd unit so it survives reboots and crashes:
sudo tee /etc/systemd/system/prometheus.service > /dev/null <<'EOF'
[Unit]
Description=Prometheus
Wants=network-online.target
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=15d \
--web.console.templates=/etc/prometheus/consoles \
--web.console.libraries=/etc/prometheus/console_libraries
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now prometheusCheck it’s alive at http://your-server-ip:9090 — you should see the built-in Prometheus web UI.
2. Install node_exporter on every server you want to monitor
Repeat this on each server you want metrics from, including the monitoring server itself if you want to monitor its own health:
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 xvf 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
sudo tee /etc/systemd/system/node_exporter.service > /dev/null <<'EOF'
[Unit]
Description=Node Exporter
Wants=network-online.target
After=network-online.target
[Service]
User=node_exporter
Group=node_exporter
Type=simple
ExecStart=/usr/local/bin/node_exporter
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now node_exporterVerify it’s exposing metrics with curl http://localhost:9100/metrics — you should get a wall of plain-text metric lines.
3. Point Prometheus at your targets
Back on the Prometheus server, edit /etc/prometheus/prometheus.yml and add a scrape job listing every server’s node_exporter endpoint:
global:
scrape_interval: 15s
evaluation_interval: 15s
scrape_configs:
- job_name: "prometheus"
static_configs:
- targets: ["localhost:9090"]
- job_name: "node_exporter"
static_configs:
- targets:
- "localhost:9100"
- "10.0.0.11:9100"
- "10.0.0.12:9100"
labels:
environment: "production"Reload the config without restarting the service:
curl -X POST http://localhost:9090/-/reloadThen go to Status > Targets in the Prometheus UI and confirm every job shows UP. If a target is DOWN, it’s almost always a firewall blocking port 9100 — check that before anything else.
4. Install Grafana
sudo apt-get install -y apt-transport-https software-properties-common wget
sudo mkdir -p /etc/apt/keyrings/
wget -q -O - https://apt.grafana.com/gpg.key | gpg --dearmor | sudo tee /etc/apt/keyrings/grafana.gpg > /dev/null
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-get update
sudo apt-get install -y grafana
sudo systemctl daemon-reload
sudo systemctl enable --now grafana-serverGrafana listens on port 3000 by default. Open http://your-server-ip:3000 and log in with the default credentials (admin/admin) — it will immediately force you to set a new password.
5. Add Prometheus as a data source
In Grafana, go to Connections > Data sources > Add data source, choose Prometheus, and set the URL to http://localhost:9090 (or the Prometheus server’s IP if Grafana is running elsewhere). Click Save & test — Grafana should confirm it can reach and query Prometheus successfully.
6. Import a ready-made server monitoring dashboard
You don’t need to build panels from scratch. Go to Dashboards > New > Import, enter dashboard ID 1860 (“Node Exporter Full” — one of the most widely used community dashboards), select your Prometheus data source, and click Import. Within seconds you’ll have CPU, memory, disk I/O, network throughput, and filesystem usage graphed for every server you added.
7. Run a couple of PromQL queries to confirm the data is real
Before you trust any dashboard, it’s worth poking at the raw data yourself. Open the Prometheus UI at http://your-server-ip:9090/graph and try a few queries in the expression browser:
# Current CPU usage percentage per instance
100 - (avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)
# Available memory in gigabytes
node_memory_MemAvailable_bytes / 1024 / 1024 / 1024
# Root filesystem usage percentage
100 - ((node_filesystem_avail_bytes{mountpoint="/"} * 100) / node_filesystem_size_bytes{mountpoint="/"})If these return real numbers instead of empty results, your scrape configuration and node_exporter are both working correctly, and the imported dashboard’s panels are pulling from the same underlying metrics. Getting comfortable with two or three PromQL patterns like this also makes it much easier to build custom panels later instead of relying entirely on community dashboards.
Common pitfalls
- Firewall and port confusion. Port 9100 needs to accept connections from the Prometheus server’s IP specifically, not the whole internet. Lock it down with your cloud provider’s security groups or
ufw allow from <prometheus-ip> to any port 9100rather than opening it broadly. - Storage growth catches people off guard. Prometheus’s default retention is 15 days, but with high-cardinality labels or a lot of hosts, the local TSDB can balloon quickly. Set
--storage.tsdb.retention.sizealongside retention time, and monitor/var/lib/prometheusdisk usage itself. - No alerting out of the box. Prometheus and Grafana will happily collect data forever without ever paging anyone. You need to separately deploy Alertmanager (or use Grafana’s built-in unified alerting) and define alert rules — this is the step most self-hosted setups skip and later regret.
- Single point of failure. If your one Prometheus server goes down, you lose visibility into everything, including the outage that took it down. For anything beyond a handful of servers, consider a second Prometheus instance or remote-write to long-term storage.
- No authentication by default. Neither Prometheus nor node_exporter ship with built-in authentication out of the box. Anyone who can reach port 9090 or 9100 can read your metrics, and anyone who can reach 9090 can also reload your config. Put both behind a firewall, VPN, or a reverse proxy with basic auth or mTLS before you consider the setup production-ready.
- Editing prometheus.yml by hand doesn’t scale. Manually adding a line to
static_configsevery time you spin up a new server works fine for five hosts and becomes tedious at fifty. Once your fleet grows, look at file-based service discovery (file_sd_configs) or a cloud provider’s native discovery mechanism so new servers get scraped automatically.
When to use this vs a SaaS tool
Self-hosted Prometheus and Grafana are hard to beat on cost — there’s no per-host or per-metric billing, and you own the data outright. They’re also extremely flexible: PromQL can answer almost any question about your infrastructure once you learn it, and the dashboard ecosystem is enormous.
The tradeoff is operational overhead. You are now responsible for patching, scaling, and backing up your monitoring stack — and if it breaks during an incident, you’re debugging your monitoring tool while also debugging the outage it was supposed to catch. A managed SaaS platform bundles alerting, on-call routing, log correlation, and APM into one polished product with a support line, which is worth paying for once a team’s time is worth more than the license fee.
In practice, the decision usually comes down to a handful of honest questions:
- Team size and time. A one- or two-person ops team can maintain Prometheus and Grafana just fine, but every hour spent patching the monitoring stack is an hour not spent on the product. Larger teams with dedicated SRE capacity absorb this more easily.
- Compliance and data residency. If contractual or regulatory requirements mean metrics can never leave your own infrastructure, self-hosting isn’t just cheaper — it may be the only option that satisfies the requirement.
- Scale. A handful of servers is trivial for Prometheus. Thousands of hosts, high-cardinality microservice metrics, or multi-region setups push you toward either a managed backend (Grafana Cloud, Thanos-as-a-service) or a commercial SaaS platform built to handle that scale for you.
- Need for extras. Built-in synthetic monitoring, uptime checks from multiple global regions, distributed tracing, and log aggregation all exist in the Prometheus/Grafana ecosystem, but you’ll be wiring together separate tools (Blackbox Exporter, Tempo, Loki) rather than getting them in one login.
If you’re a small team or a budget-conscious project, self-hosting wins. If you need enterprise SLAs, out-of-the-box distributed tracing, and don’t want to own infrastructure for your infrastructure monitoring, a managed tool is the safer bet.
FAQ
Is Prometheus and Grafana really free?
Yes — both are open source under permissive licenses (Apache 2.0 for Prometheus, AGPL for Grafana’s core). You only pay for the compute you run them on. Grafana Labs also sells a hosted Grafana Cloud service if you want the same dashboards without managing servers, but the self-hosted path described here costs nothing beyond your own infrastructure.
Can I monitor Windows servers with this setup?
Yes, using windows_exporter instead of node_exporter. It exposes an equivalent set of Windows performance counters over HTTP, and you scrape it from Prometheus exactly the same way — just add its endpoint (typically port 9182) as a new target.
How do I get alerted when something goes wrong?
Deploy Alertmanager alongside Prometheus, define alerting rules in a separate rules file (for example, high CPU or low disk space), and point Alertmanager at Slack, email, or PagerDuty for notifications. Grafana’s own alerting can also fire notifications directly from dashboard queries if you’d rather not run a separate Alertmanager instance.
How many servers can one Prometheus instance handle?
A single reasonably sized Prometheus server can comfortably scrape hundreds of targets. Once you’re monitoring hundreds of hosts or need long-term retention beyond a few months, look at remote-write to a scalable backend like Thanos, Mimir, or Cortex rather than scaling Prometheus vertically forever.
Do I need to expose ports 9090 and 3000 to the internet?
No, and you shouldn’t. Keep both behind a VPN, SSH tunnel, or reverse proxy with authentication. Only node_exporter’s port needs to be reachable, and only from the Prometheus server itself.
Once your dashboards are live, it’s worth stepping back and asking whether self-hosting is really the right long-term call for your team — see how it compares to fully managed tools like Datadog and Site24x7 before you commit to owning this stack indefinitely.