To install Prometheus on Ubuntu 24.04 with Grafana, run Prometheus and Node Exporter as dedicated systemd services, keep their web interfaces private, install Grafana from its official stable APT repository, and verify that Grafana can query live host metrics from Prometheus.
This tutorial uses a Raff Technologies Linux VM as the reference Ubuntu server. The single-VM design intentionally binds Prometheus, Node Exporter, and Grafana to 127.0.0.1, so ports 9090, 9100, and 3000 are not exposed directly to the public internet. Administrators reach Grafana through an SSH tunnel.
The previous version of this tutorial used Prometheus 3.5.5 LTS. That LTS line reached end of support on July 31, 2026. As of September 7, 2026, the supported Prometheus LTS line is 3.13, and this tutorial pins Prometheus 3.13.2 LTS. Node Exporter remains at 1.12.1. Grafana is installed from the official stable APT repository instead of being pinned to a manual package, so normal package maintenance can deliver stable Grafana updates.
The original monitoring workflow was tested on a Raff Ubuntu 24.04 VM with 2 vCPU and 4 GB RAM. Prometheus 3.13.2, Node Exporter 1.12.1, and the current Grafana repository instructions were documentation-reviewed for this refresh; no new full machine retest is claimed.
Prerequisites:
- An Ubuntu 24.04 Linux VM on Raff
- SSH access with a non-root sudo user
- SSH key authentication recommended
- Enough disk capacity for your chosen Prometheus retention period
- A recovery path before changing firewall rules
Step 1 — Prepare Ubuntu 24.04
Update packages and install the utilities used in this tutorial:
sudo apt update sudo apt upgrade -y sudo apt install -y curl wget tar gnupg ca-certificates ufw jq
If the upgrade requires a reboot, schedule it before continuing:
test -f /var/run/reboot-required && cat /var/run/reboot-required
Check the operating system and current resources:
lsb_release -ds nproc free -h df -h /
A 2 vCPU / 4 GB RAM VM is a practical starting point for this small single-node tutorial, but Prometheus storage and memory needs depend on scrape frequency, retention, target count, and metric cardinality.
Verify: Ubuntu should report 24.04 LTS and the VM should have enough CPU, memory, and free disk for the monitoring workload you intend to retain.
Step 2 — Create dedicated service users and Prometheus directories
Create non-login accounts for Prometheus and Node Exporter:
sudo useradd --system --no-create-home --shell /usr/sbin/nologin prometheus 2>/dev/null || true sudo useradd --system --no-create-home --shell /usr/sbin/nologin node_exporter 2>/dev/null || true
Create Prometheus configuration and data directories:
sudo install -d -o prometheus -g prometheus /etc/prometheus sudo install -d -o prometheus -g prometheus /var/lib/prometheus
Check the accounts and ownership:
getent passwd prometheus getent passwd node_exporter ls -ld /etc/prometheus /var/lib/prometheus
Verify: both service accounts should exist and the Prometheus directories should be owned by prometheus.
Step 3 — Install Prometheus 3.13.2 LTS and verify the download
Prometheus 3.13 is the current supported LTS line for this tutorial. Download the Linux AMD64 archive:
cd /tmp PROMETHEUS_VERSION="3.13.2" PROMETHEUS_ARCHIVE="prometheus-${PROMETHEUS_VERSION}.linux-amd64.tar.gz" curl -fLO "https://github.com/prometheus/prometheus/releases/download/v${PROMETHEUS_VERSION}/${PROMETHEUS_ARCHIVE}"
Verify the SHA-256 checksum published by Prometheus:
echo "0e8c4d46101bd025ea8265e377d2caabc57f488fc1be1c367f37db69ea41be6f ${PROMETHEUS_ARCHIVE}" | sha256sum -c -
Expected output:
prometheus-3.13.2.linux-amd64.tar.gz: OK
Extract and install the binaries:
tar -xzf "$PROMETHEUS_ARCHIVE" cd "prometheus-${PROMETHEUS_VERSION}.linux-amd64" sudo install -m 0755 prometheus promtool /usr/local/bin/
Verify both binaries:
prometheus --version | head -n 1 promtool --version | head -n 1
The output should report version 3.13.2.
Verify: the checksum should pass and both installed binaries should report Prometheus 3.13.2.
Step 4 — Configure Prometheus to scrape itself and Node Exporter
Create /etc/prometheus/prometheus.yml:
sudo tee /etc/prometheus/prometheus.yml > /dev/null <<'EOF' global: scrape_interval: 15s evaluation_interval: 15s scrape_configs: - job_name: "prometheus" static_configs: - targets: ["127.0.0.1:9090"] - job_name: "node_exporter" static_configs: - targets: ["127.0.0.1:9100"] EOF
Set ownership and validate the configuration:
sudo chown prometheus:prometheus /etc/prometheus/prometheus.yml promtool check config /etc/prometheus/prometheus.yml
Verify: promtool should report a successful configuration check and both localhost scrape targets should be present.
Step 5 — Run Prometheus under systemd on localhost only
Create the service unit:
sudo tee /etc/systemd/system/prometheus.service > /dev/null <<'EOF' [Unit] Description=Prometheus Monitoring 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.listen-address=127.0.0.1:9090 Restart=on-failure RestartSec=5 [Install] WantedBy=multi-user.target EOF
The explicit 15d retention value makes the tutorial's starting policy visible instead of leaving it implicit. Change it only after checking disk growth and deciding how much incident history you need.
Start Prometheus:
sudo systemctl daemon-reload sudo systemctl enable --now prometheus
Check the service, readiness endpoint, and bind address:
systemctl is-active prometheus curl -fsS http://127.0.0.1:9090/-/ready ss -lntp | grep ':9090'
Expected listener:
127.0.0.1:9090
Verify: Prometheus should be active and ready, with port 9090 bound only to localhost.
Step 6 — Install Node Exporter 1.12.1 and verify its checksum
Download Node Exporter:
cd /tmp NODE_EXPORTER_VERSION="1.12.1" NODE_EXPORTER_ARCHIVE="node_exporter-${NODE_EXPORTER_VERSION}.linux-amd64.tar.gz" curl -fLO "https://github.com/prometheus/node_exporter/releases/download/v${NODE_EXPORTER_VERSION}/${NODE_EXPORTER_ARCHIVE}"
Verify the official Linux AMD64 checksum:
echo "b51d8a76aa2a9156a55d501aca6276fae09e262259a5e4e831d2c2222f084e63 ${NODE_EXPORTER_ARCHIVE}" | sha256sum -c -
Extract and install the binary:
tar -xzf "$NODE_EXPORTER_ARCHIVE" sudo install -m 0755 \ "node_exporter-${NODE_EXPORTER_VERSION}.linux-amd64/node_exporter" \ /usr/local/bin/node_exporter
Verify the version:
node_exporter --version | head -n 1
Verify: the checksum should pass and Node Exporter should report version 1.12.1.
Step 7 — Run Node Exporter under systemd on localhost only
Create the service unit:
sudo tee /etc/systemd/system/node_exporter.service > /dev/null <<'EOF' [Unit] Description=Prometheus 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 --web.listen-address=127.0.0.1:9100 Restart=on-failure RestartSec=5 [Install] WantedBy=multi-user.target EOF
Start the service:
sudo systemctl daemon-reload sudo systemctl enable --now node_exporter
Check its metrics and listener:
systemctl is-active node_exporter curl -fsS http://127.0.0.1:9100/metrics | head ss -lntp | grep ':9100'
Verify: Node Exporter should be active, its /metrics endpoint should return Prometheus-format metrics, and port 9100 should be bound only to localhost.
Step 8 — Verify Prometheus is scraping both targets
Wait for at least one scrape interval:
sleep 20
Inspect active targets:
curl -fsS http://127.0.0.1:9090/api/v1/targets | \ jq -r '.data.activeTargets[] | [.labels.job, .health, .lastError] | @tsv'
Expected health values:
prometheus up node_exporter up
Confirm Prometheus has actual host metrics:
curl -fsSG http://127.0.0.1:9090/api/v1/query \ --data-urlencode 'query=node_uname_info' | \ jq -r '.data.result | length'
The result should be greater than 0.
Verify: both scrape targets should report up and a Node Exporter query should return at least one result.
Step 9 — Install Grafana from the official stable APT repository
Install the repository prerequisites:
sudo apt-get install -y apt-transport-https wget gnupg
Add the Grafana Labs signing key:
sudo mkdir -p /etc/apt/keyrings sudo wget -O /etc/apt/keyrings/grafana.asc https://apt.grafana.com/gpg-full.key sudo chmod 644 /etc/apt/keyrings/grafana.asc
Add the stable repository:
echo "deb [signed-by=/etc/apt/keyrings/grafana.asc] https://apt.grafana.com stable main" | \ sudo tee /etc/apt/sources.list.d/grafana.list
Install Grafana OSS:
sudo apt-get update sudo apt-get install -y grafana
Keep Grafana private by binding it to loopback:
sudo sed -i 's/^;\?http_addr =.*/http_addr = 127.0.0.1/' /etc/grafana/grafana.ini
Enable and start Grafana:
sudo systemctl enable --now grafana-server
Check its health and listener:
systemctl is-active grafana-server curl -fsS http://127.0.0.1:3000/api/health | jq ss -lntp | grep ':3000'
Verify: Grafana should be active, its health endpoint should respond, and port 3000 should be bound only to 127.0.0.1.
Step 10 — Review UFW safely without risking SSH lockout
Because all three monitoring services are already loopback-only, you do not need public firewall rules for 3000, 9090, or 9100.
Inspect UFW before changing it:
sudo ufw status verbose
Confirm the actual SSH listening port:
sudo sshd -T | awk '/^port / {print $2}'
If UFW is already active, verify the existing SSH rule is correct and confirm no monitoring-port allow rules are present:
sudo ufw status numbered
If UFW is inactive and you want to enable it, allow the actual SSH path first. For standard SSH with the OpenSSH profile:
sudo ufw allow OpenSSH
For a custom SSH port, allow that port instead, for example:
sudo ufw allow 2222/tcp
Keep your current SSH session open and create a second SSH session to verify access. Only after the second session succeeds should you enable UFW:
sudo ufw enable sudo ufw status numbered
Do not add public allow rules for ports 3000, 9090, or 9100.
Verify: SSH should remain reachable after the firewall change and the monitoring ports should still have no public UFW allow rules.
Step 11 — Open Grafana through an SSH tunnel
From your workstation, create a local tunnel:
ssh -L 3000:127.0.0.1:3000 your-user@your-server-ip
Keep the SSH session open and browse to:
http://127.0.0.1:3000
Grafana's documented first-login defaults are:
Username: admin Password: admin
Change the administrator password when prompted.

The screenshot is preserved from the original Raff-tested workflow. Labels or minor visual details may differ in newer stable Grafana releases, but the first-login flow remains the same.
From the server, confirm all monitoring listeners are private:
ss -lntp | grep -E ':3000|:9090|:9100'
Verify: Grafana should open through the SSH tunnel, and all three monitoring listeners should show 127.0.0.1.
Step 12 — Connect Grafana to Prometheus
In Grafana, open:
Connections → Data sources → Add data source → Prometheus
Use this Prometheus server URL:
http://127.0.0.1:9090
Click Save & test.

The screenshot is retained from the original tested setup; navigation labels can move between Grafana releases.
Verify the same Prometheus API directly from Ubuntu:
curl -fsSG http://127.0.0.1:9090/api/v1/query \ --data-urlencode 'query=up' | jq -r '.status'
Expected result:
success
Verify: Grafana should accept the data source and the local Prometheus query should succeed.
Step 13 — Import a Node Exporter dashboard
In Grafana, open:
Dashboards → New → Import
The original Raff-tested workflow used Grafana.com dashboard ID:
1860
Select your Prometheus data source and import the dashboard.

Dashboard 1860 is a community dashboard rather than part of Prometheus itself. If it changes or becomes unavailable, the monitoring stack is still valid. Create a simple Grafana panel with a direct PromQL query such as:
100 - (avg by(instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)
Verify: either the imported dashboard or a manually created panel should return Node Exporter data from Prometheus.
Step 14 — Verify Prometheus and Grafana end to end
Open the Node Exporter dashboard through the SSH tunnel, use a recent time range such as Last 15 minutes, and refresh it.

Run the final server checks:
systemctl is-active prometheus systemctl is-active node_exporter systemctl is-active grafana-server prometheus --version | head -n 1 node_exporter --version | head -n 1 curl -fsS http://127.0.0.1:9090/-/ready curl -fsS http://127.0.0.1:9100/metrics >/dev/null curl -fsS http://127.0.0.1:3000/api/health | jq curl -fsS http://127.0.0.1:9090/api/v1/targets | \ jq -r '.data.activeTargets[] | [.labels.job, .health, .lastError] | @tsv' curl -fsSG http://127.0.0.1:9090/api/v1/query \ --data-urlencode 'query=node_uname_info' | \ jq -r '.data.result | length' ss -lntp | grep -E ':3000|:9090|:9100' sudo ufw status numbered
In Grafana, confirm that:
- You can sign in through the SSH tunnel.
- The Prometheus data source passes its connection test.
- A Node Exporter dashboard or manual panel displays current host metrics.
- The dashboard continues to update as new samples are scraped.
End-to-end verification is complete when Prometheus 3.13.2, Node Exporter 1.12.1, and Grafana are active; both Prometheus targets are up; Grafana can query Prometheus; live host metrics appear in a dashboard; and ports 3000, 9090, and 9100 remain loopback-only.
