← Start of tutorial

WIUNIX ACADEMY

Build a Prometheus + Grafana monitoring stack

Page 2 of 5. Review the earlier steps and prerequisites before continuing.

YOUR SYSTEM. YOUR STEPS.

Where are you working?

Choose your OS and version. The prerequisites, commands and troubleshooting follow your environment.

Ubuntu 26.10Reviewed against official documentation ·

USE YOUR OWN VALUES

Configure this tutorial

Set the example values for your environment; related explanations and copy panels update together. Never enter passwords, private keys or tokens. Values stay in this tab and are not sent to the server or share links. Check actual DNS, certificates and network policy before running commands.

Different IP addresses, domains or ports? Open the settings below, change the values and select “Apply to tutorial”, then copy the updated commands.

Set values for my environment2 editable values

Open the next configuration file

Open the file, paste the contents in the following panel, then save and exit (nano: Ctrl+O, Enter, Ctrl+X). Do not run file contents as shell commands.

Bash

Monitoring server terminal.

sudo nano /etc/systemd/system/prometheus.service
How do I know it worked?

The command completes successfully; stop and inspect any error before continuing.

06

LET’S BUILD

Create the Prometheus systemd service

This service keeps Prometheus under systemd control and stores metrics in /var/lib/prometheus. The web interface is bound to 127.0.0.1:9090 so the host does not publish Prometheus directly to the network by default.

If you later need remote access, expose it through a trusted reverse proxy or a controlled tunnel rather than widening access immediately.

systemd · /etc/systemd/system/prometheus.service

File contents; save in the named file, not in the shell.

[Unit]
Description=Prometheus Monitoring Server
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

[Install]
WantedBy=multi-user.target
How do I know it worked?

The systemd unit file exists and matches the Prometheus paths created in the earlier steps.

07

LET’S BUILD

Download and install node_exporter

node_exporter publishes host metrics such as CPU, memory, filesystem and network counters in Prometheus format. The current official download page lists 1.12.1 as the latest release at the review date.

Install only the binary on the monitoring server for now. You will reuse the same pattern on any additional Linux node that you want to monitor later.

Bash

Monitoring server terminal.

(
set -euo pipefail
[ "$(uname -m)" = x86_64 ] || { echo 'This download requires amd64/x86_64' >&2; exit 1; }
work=$(mktemp -d)
cd "$work"
curl -fL --connect-timeout 10 --max-time 180 -o node_exporter-1.12.1.linux-amd64.tar.gz \
  https://github.com/prometheus/node_exporter/releases/download/v1.12.1/node_exporter-1.12.1.linux-amd64.tar.gz
printf '%s  %s\n' 'b51d8a76aa2a9156a55d501aca6276fae09e262259a5e4e831d2c2222f084e63' 'node_exporter-1.12.1.linux-amd64.tar.gz' | sha256sum --check --strict -
tar -xzf node_exporter-1.12.1.linux-amd64.tar.gz
cd node_exporter-1.12.1.linux-amd64
sudo install -o root -g root -m 0755 node_exporter /usr/local/bin/
node_exporter --version
)
How do I know it worked?

SHA256 reports OK before extraction; node_exporter reports 1.12.1. Keep the temporary download directory until validation is complete.

Open the next configuration file

Open the file, paste the contents in the following panel, then save and exit (nano: Ctrl+O, Enter, Ctrl+X). Do not run file contents as shell commands.

Bash

Monitoring server terminal.

sudo nano /etc/systemd/system/node_exporter.service
How do I know it worked?

The command completes successfully; stop and inspect any error before continuing.

08

LET’S BUILD

Create the node_exporter systemd service

Run the basic host collectors on loopback. The optional systemd collector is omitted because service metrics need additional D-Bus access; host CPU, memory and filesystem metrics do not need it.

systemd · /etc/systemd/system/node_exporter.service

File contents; save in the named file, not in the shell.

[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

[Install]
WantedBy=multi-user.target
How do I know it worked?

The node_exporter systemd unit is present and refers to the installed binary path.

09

LET’S BUILD

Start Prometheus and node_exporter, then verify exact HTTP 200 responses

Validate as the service user before starting either service. The bounded readiness loop accepts only HTTP 200 and rejects redirects, transport failures and timeouts; service active alone is insufficient.

The Prometheus ready endpoint proves it can serve queries. The next step separately confirms that both targets have actually been scraped.

Bash

Monitoring server terminal.

(
set -euo pipefail
sudo chown root:prometheus /etc/prometheus/prometheus.yml
sudo chmod 0640 /etc/prometheus/prometheus.yml
sudo -u prometheus /usr/local/bin/promtool check config /etc/prometheus/prometheus.yml
sudo systemctl daemon-reload
sudo systemctl enable --now prometheus node_exporter
systemctl is-active prometheus node_exporter
ok=0
for attempt in $(seq 1 12); do
  if code=$(curl -sS --connect-timeout 2 --max-time 5 -o /dev/null -w '%{http_code}' http://127.0.0.1:9090/-/ready); then
    if [ "$code" = 200 ]; then ok=1; break; fi
  fi
  sleep 2
done
[ "$ok" = 1 ] || { echo 'Endpoint did not become ready: http://127.0.0.1:9090/-/ready' >&2; exit 1; }
printf '%s -> 200\n' 'http://127.0.0.1:9090/-/ready'
ok=0
for attempt in $(seq 1 12); do
  if code=$(curl -sS --connect-timeout 2 --max-time 5 -o /dev/null -w '%{http_code}' http://127.0.0.1:9100/metrics); then
    if [ "$code" = 200 ]; then ok=1; break; fi
  fi
  sleep 2
done
[ "$ok" = 1 ] || { echo 'Endpoint did not become ready: http://127.0.0.1:9100/metrics' >&2; exit 1; }
printf '%s -> 200\n' 'http://127.0.0.1:9100/metrics'
)
EXAMPLE OUTPUT
Illustrative success (additional promtool messages omitted):
active
active
http://127.0.0.1:9090/-/ready -> 200
http://127.0.0.1:9100/metrics -> 200
How do I know it worked?

Both services report active, and each checked URL prints an exact 200 status code.

Prometheus does not stay active

Inspect the recent journal entries and validate that the configuration file and storage path are readable by the Prometheus service account.

Bash
sudo journalctl -u prometheus -n 80 --no-pager
sudo ls -ld /etc/prometheus /var/lib/prometheus
sudo ls -l /etc/prometheus/prometheus.yml
sudo -u prometheus /usr/local/bin/promtool check config /etc/prometheus/prometheus.yml
node_exporter is not listening on port 9100

Check the service log and confirm that no other process already owns the chosen listen address.

Bash
sudo journalctl -u node_exporter -n 80 --no-pager
sudo ss -lntp | grep 9100 || true
Explore all tutorials ↗