Node Exporter is the official Prometheus exporter for Linux host metrics: CPU, memory, disk space and I/O, network traffic, file descriptors and more. It runs as a small daemon on every server you want to monitor and serves the metrics over HTTP on port 9100, where Prometheus scrapes them. In this tutorial you will install Node Exporter on Ubuntu 24.04 from the official release, run it as a hardened systemd service, protect the endpoint with TLS and basic authentication, and add the server to Prometheus.
Prerequisites
To follow this guide you need:
- A server running Ubuntu 24.04 LTS to monitor, for example a CubePath VPS, with a non-root
sudouser. - A Prometheus server that can reach this server on TCP port
9100. Its IP address is referred to asprometheus_ip, and the monitored server's address asyour_server_ip. - UFW enabled on the monitored server, or another firewall you manage.
Step 1 - Creating a system user
Node Exporter does not need any privileges, so run it under a dedicated system account without a shell or home directory:
sudo useradd --system --no-create-home --shell /usr/sbin/nologin node_exporter
Confirm that the account exists:
id node_exporter
uid=998(node_exporter) gid=998(node_exporter) groups=998(node_exporter)
Step 2 - Downloading and verifying Node Exporter
Ubuntu packages Node Exporter as prometheus-node-exporter, but that version lags behind upstream. This guide installs the official binary instead. Check the latest version on the releases page and set it in a variable, together with your architecture (amd64 for x86_64, arm64 for ARM servers):
VERSION=1.9.1
ARCH=amd64
Download the archive and the checksum file for that release into a temporary directory:
cd /tmp
curl -fLO "https://github.com/prometheus/node_exporter/releases/download/v${VERSION}/node_exporter-${VERSION}.linux-${ARCH}.tar.gz"
curl -fLO "https://github.com/prometheus/node_exporter/releases/download/v${VERSION}/sha256sums.txt"
Verify the archive against the published checksum:
sha256sum --check --ignore-missing sha256sums.txt
node_exporter-1.9.1.linux-amd64.tar.gz: OK
Extract it and install the binary:
tar xzf "node_exporter-${VERSION}.linux-${ARCH}.tar.gz"
sudo install -m 0755 -o root -g root "node_exporter-${VERSION}.linux-${ARCH}/node_exporter" /usr/local/bin/
node_exporter --version
node_exporter, version 1.9.1 (branch: HEAD, revision: ...)
Step 3 - Running Node Exporter with systemd
Create a directory for the textfile collector, which lets your own scripts publish extra metrics (you will use it in Step 7):
sudo install -d -m 0755 -o node_exporter -g node_exporter /var/lib/node_exporter/textfile_collector
Create the service unit:
sudo nano /etc/systemd/system/node_exporter.service
[Unit]
Description=Prometheus Node Exporter
Documentation=https://github.com/prometheus/node_exporter
Wants=network-online.target
After=network-online.target
[Service]
User=node_exporter
Group=node_exporter
ExecStart=/usr/local/bin/node_exporter \
--web.listen-address=:9100 \
--collector.systemd \
--collector.textfile.directory=/var/lib/node_exporter/textfile_collector
Restart=on-failure
NoNewPrivileges=yes
ProtectSystem=strict
ProtectHome=read-only
PrivateTmp=yes
[Install]
WantedBy=multi-user.target
--collector.systemd is disabled by default; it adds the state of every systemd unit, which is useful for alerting on failed services. The Protect* settings make the file system read-only for the process, which only needs to read /proc and /sys.
Start the service and enable it at boot:
sudo systemctl daemon-reload
sudo systemctl enable --now node_exporter
systemctl status node_exporter --no-pager
● node_exporter.service - Prometheus Node Exporter
Loaded: loaded (/etc/systemd/system/node_exporter.service; enabled; preset: enabled)
Active: active (running) since Thu 2026-09-25 15:30:12 UTC; 3s ago
Query the metrics locally to confirm it is collecting data:
curl -s http://localhost:9100/metrics | grep -E '^node_(load1|memory_MemAvailable_bytes) '
node_load1 0.08
node_memory_MemAvailable_bytes 1.498537984e+09
Step 4 - Restricting access with the firewall
The metrics reveal a lot about the server (mount points, network interfaces, running services), so only Prometheus should reach port 9100. Allow it from the Prometheus server only:
sudo ufw allow from prometheus_ip to any port 9100 proto tcp
sudo ufw status
To Action From
-- ------ ----
22/tcp ALLOW Anywhere
9100/tcp ALLOW prometheus_ip
If Prometheus reaches the server over a private network, use its private address in the rule.
Step 5 - Enabling TLS and basic authentication
The firewall limits who can connect, but the metrics still travel in plain text. Node Exporter supports TLS and basic authentication through a web configuration file. Install the tools to create a bcrypt password hash:
sudo apt install apache2-utils
Generate a hash for a strong password. The command prompts for the password twice and prints only the hash:
htpasswd -nBC 12 "" | tr -d ':\n'; echo
$2y$12$Qm0w2...Zk6
Create a self-signed certificate valid for the server's IP address. Replace your_server_ip with the address Prometheus uses to connect:
sudo mkdir -p /etc/node_exporter
sudo openssl req -x509 -newkey rsa:2048 -nodes -days 3650 \
-keyout /etc/node_exporter/node_exporter.key \
-out /etc/node_exporter/node_exporter.crt \
-subj "/CN=node-exporter" \
-addext "subjectAltName = IP:your_server_ip"
Create the web configuration file with the certificate paths and the hash you generated:
sudo nano /etc/node_exporter/web.yml
tls_server_config:
cert_file: /etc/node_exporter/node_exporter.crt
key_file: /etc/node_exporter/node_exporter.key
basic_auth_users:
prometheus: $2y$12$Qm0w2...Zk6
Give the service user read access to the key and configuration:
sudo chown -R root:node_exporter /etc/node_exporter
sudo chmod 0640 /etc/node_exporter/node_exporter.key /etc/node_exporter/web.yml
Edit the unit and add the --web.config.file flag to ExecStart:
sudo nano /etc/systemd/system/node_exporter.service
ExecStart=/usr/local/bin/node_exporter \
--web.listen-address=:9100 \
--web.config.file=/etc/node_exporter/web.yml \
--collector.systemd \
--collector.textfile.directory=/var/lib/node_exporter/textfile_collector
Apply the change:
sudo systemctl daemon-reload
sudo systemctl restart node_exporter
Verify that requests without credentials are rejected and authenticated requests work. The second command prompts for the password:
curl -s -o /dev/null -w '%{http_code}\n' --cacert /etc/node_exporter/node_exporter.crt https://your_server_ip:9100/metrics
curl -s -u prometheus --cacert /etc/node_exporter/node_exporter.crt https://your_server_ip:9100/metrics | grep '^node_load1 '
401
Enter host password for user 'prometheus':
node_load1 0.05
Step 6 - Adding the server to Prometheus
On the Prometheus server, copy /etc/node_exporter/node_exporter.crt from the monitored server to /etc/prometheus/node_exporter.crt, and store the plain-text password in a file readable by the prometheus user:
echo 'your_strong_password' | sudo tee /etc/prometheus/node_exporter_password > /dev/null
sudo chown prometheus:prometheus /etc/prometheus/node_exporter_password
sudo chmod 0600 /etc/prometheus/node_exporter_password
Add a scrape job to /etc/prometheus/prometheus.yml under scrape_configs:
scrape_configs:
- job_name: node
scheme: https
tls_config:
ca_file: /etc/prometheus/node_exporter.crt
basic_auth:
username: prometheus
password_file: /etc/prometheus/node_exporter_password
static_configs:
- targets: ['your_server_ip:9100']
labels:
instance: web01
Validate the file and reload Prometheus:
promtool check config /etc/prometheus/prometheus.yml
sudo systemctl reload prometheus
Checking /etc/prometheus/prometheus.yml
SUCCESS: /etc/prometheus/prometheus.yml is valid prometheus config file syntax
Open the Prometheus web interface, go to Status > Targets and check that the node job shows the target as UP. You can also run the query up{job="node"}, which returns 1 for each healthy target.
Step 7 - Publishing custom metrics with the textfile collector (optional)
The textfile collector exposes any *.prom file in its directory as metrics. This is the right place for values that only a script can compute, such as the number of pending package upgrades. Write the file atomically (to a temporary name, then rename it) so Node Exporter never reads a half-written file:
sudo nano /usr/local/bin/apt-upgrades-metric
#!/usr/bin/env bash
set -euo pipefail
dir=/var/lib/node_exporter/textfile_collector
count="$(apt list --upgradable 2>/dev/null | grep -c upgradable || true)"
cat > "${dir}/apt_upgrades.prom.$$" <<EOF
# HELP apt_upgrades_pending Number of packages with pending upgrades.
# TYPE apt_upgrades_pending gauge
apt_upgrades_pending ${count}
EOF
mv "${dir}/apt_upgrades.prom.$$" "${dir}/apt_upgrades.prom"
Make it executable, run it once, and schedule it every hour in root's crontab (sudo crontab -e, then add 0 * * * * /usr/local/bin/apt-upgrades-metric):
sudo chmod 0755 /usr/local/bin/apt-upgrades-metric
sudo /usr/local/bin/apt-upgrades-metric
curl -s -u prometheus --cacert /etc/node_exporter/node_exporter.crt https://your_server_ip:9100/metrics | grep '^apt_upgrades_pending'
apt_upgrades_pending 3
Step 8 - Adding basic alert rules
With the metrics in Prometheus, a few rules cover the most common host problems. Create a rules file on the Prometheus server:
sudo nano /etc/prometheus/rules/node.yml
groups:
- name: node
rules:
- alert: NodeDown
expr: up{job="node"} == 0
for: 5m
labels:
severity: critical
annotations:
summary: "{{ $labels.instance }} is not reachable"
- alert: DiskAlmostFull
expr: node_filesystem_avail_bytes{fstype!~"tmpfs|overlay"} / node_filesystem_size_bytes < 0.10
for: 15m
labels:
severity: warning
annotations:
summary: "{{ $labels.instance }} {{ $labels.mountpoint }} has less than 10% free"
- alert: SystemdUnitFailed
expr: node_systemd_unit_state{state="failed"} == 1
for: 5m
labels:
severity: warning
annotations:
summary: "{{ $labels.name }} failed on {{ $labels.instance }}"
Reference the file in prometheus.yml if your configuration does not already load that directory:
rule_files:
- /etc/prometheus/rules/*.yml
Validate the rules and reload Prometheus:
promtool check rules /etc/prometheus/rules/node.yml
sudo systemctl reload prometheus
Checking /etc/prometheus/rules/node.yml
SUCCESS: 3 rules found
The rules appear under Alerts in the Prometheus web interface. To receive notifications, connect Prometheus to Alertmanager.
Troubleshooting
The target shows connection refused or context deadline exceeded: the service is not running or the firewall blocks Prometheus. Check systemctl status node_exporter and the UFW rule, and test from the Prometheus server with curl -k https://your_server_ip:9100/metrics.
The target shows x509: certificate is valid for ..., not ...: the address in targets does not match the subjectAltName of the certificate. Regenerate the certificate with the address Prometheus uses.
The target shows 401 Unauthorized: the password file on the Prometheus server does not match the bcrypt hash in web.yml. Generate a new hash from the password in the file.
Node Exporter fails to start after adding web.yml: read journalctl -u node_exporter -n 20. The usual causes are a YAML indentation error or the node_exporter user not being able to read the key file.
Custom metrics do not appear: the file must end in .prom, be readable by node_exporter, and follow the Prometheus text format. Errors are reported in the node_textfile_scrape_error metric.
Conclusion
Node Exporter now runs as a hardened systemd service on Ubuntu 24.04, serves its metrics only over authenticated TLS to your Prometheus server, and feeds alert rules for downtime, disk space and failed units. Next, add Alertmanager to route those alerts to email or chat, import a Node Exporter dashboard in Grafana, and repeat Steps 1 to 5 on every server you want to monitor, ideally with a configuration management tool.
