Skip to content
DevOps devops monitoring 5 min read

Monitoring Servers with Node Exporter

When you run a server you need to know how healthy it is. Is the CPU pinned at 100%? Is the disk about to fill up? Is memory running out? Node Exporter answers all of these. It is a small program that reads metrics (numbers that describe the state of your machine, like “CPU is 40% busy”) straight from the Linux kernel and publishes them in a format that Prometheus can collect. Install it once on every server you own, point Prometheus at it, and you get a live picture of your fleet.

What is Node Exporter

Node Exporter is the official Prometheus “exporter” for hardware and operating-system metrics on Linux. An exporter is a small web server that converts data from somewhere (here, the kernel) into the plain-text format Prometheus understands. It runs on each machine you want to watch and listens on port 9100. When asked, it returns hundreds of metrics: CPU usage, memory, disk space, disk I/O, network traffic, system load, file descriptors, and more.

The word “node” here means a single machine (a server, a VM, or a physical box), not a Node.js process. Run one Node Exporter per machine.

When to use it (and when not to)

GoalUse Node Exporter?
Monitor a Linux server’s CPU, RAM, disk, networkYes — this is exactly its job
Monitor a Windows machineNo — use windows_exporter instead
Monitor your own app’s request counts / errorsNo — instrument your app, or use a dedicated exporter
Monitor a database’s internal statsNo — use postgres_exporter, mysqld_exporter, etc.

Node Exporter watches the host, not the applications running on it. Pair it with app-level metrics for the full picture.

Step 1: Create a dedicated user

For security, run Node Exporter as its own unprivileged user that cannot log in. This way, if the process is ever compromised, the attacker has almost no power.

sudo useradd --no-create-home --shell /usr/sbin/nologin node_exporter

This creates a user named node_exporter with no home directory and no login shell.

Step 2: Download and install the binary

Grab the latest release from the official Prometheus GitHub releases. Check github.com/prometheus/node_exporter/releases for the current version number — at the time of writing it is 1.8.2.

cd /tmp
curl -LO 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 mv node_exporter-1.8.2.linux-amd64/node_exporter /usr/local/bin/
sudo chown node_exporter:node_exporter /usr/local/bin/node_exporter

Output:

node_exporter-1.8.2.linux-amd64/
node_exporter-1.8.2.linux-amd64/LICENSE
node_exporter-1.8.2.linux-amd64/NOTICE
node_exporter-1.8.2.linux-amd64/node_exporter

Verify it runs:

node_exporter --version

Output:

node_exporter, version 1.8.2 (branch: HEAD, revision: f1e0e8360aa60b6cb5e5cc1560b906261213e8c1)
  build user:       root@544e3f3a3e8c
  build date:       20240714-12:10:30
  go version:       go1.22.5

Always download exporters from the official prometheus/node_exporter GitHub releases over HTTPS. Random third-party .deb files or copy-pasted install scripts are a classic supply-chain attack vector. The curl -LO above follows redirects (-L) and saves with the original filename (-O).

Step 3: Run it as a systemd service

You want Node Exporter to start automatically on boot and restart if it crashes. systemd (the service manager built into Ubuntu) handles both. Create a unit file:

sudo nano /etc/systemd/system/node_exporter.service

Paste in the following:

[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
Restart=on-failure

[Install]
WantedBy=multi-user.target

Now reload systemd, then enable and start the service:

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

enable --now does two things at once: it enables the service (start on every boot) and starts it immediately.

Output:

● node_exporter.service - Prometheus Node Exporter
     Loaded: loaded (/etc/systemd/system/node_exporter.service; enabled; preset: enabled)
     Active: active (running) since Mon 2026-06-15 09:14:02 UTC; 3s ago
   Main PID: 4821 (node_exporter)
      Tasks: 5 (limit: 4612)
     Memory: 8.3M

Confirm metrics are being served. The node_exporter listens on 0.0.0.0:9100/metrics:

curl -s http://localhost:9100/metrics | head -n 5

Output:

# HELP go_gc_duration_seconds A summary of the pause duration of garbage collection cycles.
# TYPE go_gc_duration_seconds summary
go_gc_duration_seconds{quantile="0"} 1.2e-05
go_gc_duration_seconds{quantile="0.25"} 1.8e-05
go_gc_duration_seconds{quantile="0.5"} 2.4e-05

Step 4: Open the firewall (carefully)

If Prometheus runs on a different server, it needs to reach port 9100. Ubuntu uses ufw (Uncomplicated Firewall) to manage which ports are open. Do not open port 9100 to the whole internet — anyone could read your server metrics. Allow only the Prometheus server’s IP:

sudo ufw allow from 10.0.0.5 to any port 9100 proto tcp

Replace 10.0.0.5 with your Prometheus server’s IP. If Prometheus runs on the same machine, skip this entirely — localhost traffic does not touch the firewall.

Step 5: Add it to Prometheus scrape config

Prometheus “scrapes” (regularly fetches) the /metrics endpoint. Tell it where Node Exporter lives by editing /etc/prometheus/prometheus.yml on the Prometheus server:

scrape_configs:
  - job_name: "node"
    static_configs:
      - targets:
          - "10.0.0.10:9100"   # web server
          - "10.0.0.11:9100"   # db server
        labels:
          env: "production"

Use localhost:9100 if everything is on one box. Reload Prometheus so it picks up the change without a full restart:

sudo systemctl reload prometheus

In the Prometheus web UI, go to Status → Targets. Your node targets should show UP in green.

Step 6: Query basic host metrics

Open the Prometheus UI (default http://your-prometheus:9090) and run these PromQL queries (PromQL is Prometheus’s query language).

CPU usage (percent busy, per instance):

100 - (avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)

This takes the rate of idle CPU time over 5 minutes and subtracts it from 100, giving the percentage the CPU was actually busy.

Memory used (percent):

100 * (1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes))

Disk space free on the root filesystem (percent):

100 * (node_filesystem_avail_bytes{mountpoint="/"} / node_filesystem_size_bytes{mountpoint="/"})

Network bytes received per second:

rate(node_network_receive_bytes_total{device!="lo"}[5m])

The device!="lo" filter excludes the loopback interface so you only see real traffic.

MetricWhat it tells you
node_cpu_seconds_totalCPU time per core, per mode (idle, user, system)
node_memory_MemAvailable_bytesRAM available for new processes
node_filesystem_avail_bytesFree disk space per mount
node_load1 / node_load5System load average (1 and 5 minutes)
node_network_receive_bytes_totalTotal bytes received per interface

Best Practices

  • Run one Node Exporter per machine as a dedicated node_exporter system user — never as root.
  • Pin the version in your install script and upgrade deliberately, rather than blindly pulling “latest”.
  • Restrict port 9100 with ufw so only your Prometheus server can reach it; never expose it publicly.
  • Add a consistent instance and env label in the scrape config so you can group servers in dashboards and alerts.
  • Build alerts on top of these metrics (disk > 85% full, memory > 90%, CPU saturated for 10 minutes) so you find problems before users do.
  • Keep Restart=on-failure in the unit file so a crashed exporter recovers on its own.
Last updated June 15, 2026
Was this helpful?