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)
| Goal | Use Node Exporter? |
|---|---|
| Monitor a Linux server’s CPU, RAM, disk, network | Yes — this is exactly its job |
| Monitor a Windows machine | No — use windows_exporter instead |
| Monitor your own app’s request counts / errors | No — instrument your app, or use a dedicated exporter |
| Monitor a database’s internal stats | No — 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_exporterGitHub releases over HTTPS. Random third-party.debfiles or copy-pasted install scripts are a classic supply-chain attack vector. Thecurl -LOabove 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.
| Metric | What it tells you |
|---|---|
node_cpu_seconds_total | CPU time per core, per mode (idle, user, system) |
node_memory_MemAvailable_bytes | RAM available for new processes |
node_filesystem_avail_bytes | Free disk space per mount |
node_load1 / node_load5 | System load average (1 and 5 minutes) |
node_network_receive_bytes_total | Total bytes received per interface |
Best Practices
- Run one Node Exporter per machine as a dedicated
node_exportersystem user — never as root. - Pin the version in your install script and upgrade deliberately, rather than blindly pulling “latest”.
- Restrict port
9100withufwso only your Prometheus server can reach it; never expose it publicly. - Add a consistent
instanceandenvlabel 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-failurein the unit file so a crashed exporter recovers on its own.