Configuring Node Exporter correctly means setting up the service so that Prometheus collects reliable Linux server metrics with clearly defined ports, targeted collector settings, and robust security measures. In this practical guide, I’ll walk you through installation, systemd setup, collector tuning, security, Prometheus integration, performance tips, and useful checks for everyday use.
Key points
- Installation and starting the service with its own systemd unit
- Collector Choose carefully, reduce metric load
- Security through port forwarding and proxies
- Prometheus Scrapes, Alerts, and Storage
- Performance via Intervals, Sharding, Cleanup
What is the Node Exporter?
I set the Node Set up the exporter on every Linux host to provide system metrics in Prometheus format. The daemon provides data on CPU load, system load, memory, swap, file systems, network, and—optionally—systemd and process data. I access the endpoint via HTTP /metrics and see readable time series that Prometheus fetches cyclically. This approach works well with heterogeneous server fleets and remains transparent thanks to the pull model. I benefit from a clear separation: the exporter collects the data, while Prometheus stores and analyzes it.
Architecture Overview: How Node Exporter and Prometheus Work Together
I'm starting the exporter on port 9100, I manage the collectors and have Prometheus scrape them periodically. The pull model simplifies firewall configuration because I only need to allow access from Prometheus to the host. Grafana or similar visualization solutions then connect to Prometheus and display the metrics in a clear and organized manner. In production environments, I run multiple Prometheus servers for separate responsibilities. This way, I keep paths short, roles clear, and administration transparent.
Installation on Linux: Clean and Reproducible
I'm downloading the appropriate binary for linux-amd64 or the target architecture, and adjust it accordingly /usr/local/bin/. Next, I'll set up a system user without a login, for example node_exporter, and set the ownership permissions on the binary. To start it automatically, I create a systemd unit in the directory /etc/systemd/system/ with a simple ExecStart and a Restart policy. After systemctl daemon-reload I enable and start the service, check its status, and call curl http://localhost:9100/metrics ... This way, I can immediately tell whether the metrics are available and whether the service is running as intended.
Node Exporter as a systemd Service: The Key Configuration Options
I define the following in the unit User and "Group" as a dedicated account, set Type=simple and a clear ExecStart. A restart strategy such as Restart=on-failure helps prevent short-term outages. For updates, I edit the unit or create a drop-in file so that changes remain traceable. After each adjustment, I run a daemon-reload and restart the service. I keep the unit compact, well-documented, and reusable across all server classes.
Port and List Address: Consistent and Secure
By default, I listen on port 9100, but change the port on a per-project basis if there are conflicts. The option --web.listen-address allows you to customize the host and port, such as 127.0.0.1:9200 for local proxy offloading. A consistent port scheme reduces confusion in large teams. I enter port changes centrally to ensure that firewalls and security lists are configured correctly. The port is restricted to the Prometheus servers and does not have unrestricted access to the Internet.
Configure Collector Specifically: Only What Really Matters
I choose the Collector deliberately, to control data volume and processing time. Standard modules for the CPU, memory, file systems, and networking usually remain active. If necessary, I activate special modules such as --collector.systemd or --collector.processes, to monitor services and processes more closely. I disable unwanted modules using --no-collector.X, so that Prometheus has fewer time series to process. I document the selections made for each server role to ensure consistency across the team.
Textfile Collector: Feeding Your Own Metrics Cleanly
I use the Text File Collector for individual Key metrics that standard modules do not provide. A directory such as /var/lib/node_exporter/textfile_collector collects .promPrometheus-format files. Scripts write data atomically by creating temporary files and replacing them at the end, so that no incomplete values appear. This is how I feed business metrics, batch statuses, or queue lengths directly into Prometheus. I follow naming conventions to ensure that reports and dashboards are easy to read.
Security in Production: Access Restricted to Authorized Users Only
I restrict port access by Firewall consistently to the scrape sources. An upstream reverse proxy handles TLS or mTLS as needed and handles authentication. I run the service without root privileges and assign minimal file permissions for log and text file paths. In separate networks, I provide additional security via VPN or private subnets. This ensures that detailed system information remains protected and visible only to the monitoring infrastructure.
Integration with Prometheus: Scrapes, Labels, Alerts
I'm putting in the prometheus.yml a job like job_name: node , set a reasonable scrape_interval (often 15 seconds) and configure targets or service discovery. Consistent labels (e.g., environment, role, location) make filtering and dashboards easier. For frequent analyses, I define recording rules, thereby reducing the load on ad-hoc queries. Alerts are triggered based on aggregated metrics, such as CPU load, RAM, swap, disk usage, and network errors. For an introduction to utilization and peak loads, I refer you to my concise CPU and Load Analysis, which explains key metrics in a practical way.
Monitoring the Node Exporter Itself: Trust Is Good, but Verification Is Better
I'm watching the Job Status in Prometheus and set up alerts to trigger if a host hasn't been scraped for an extended period. I regularly check for new versions of the exporters to take advantage of bug fixes and new modules as soon as possible. In addition, I monitor the number of time series per host to detect early on if a collector change is driving up the load. Dashboards display panel notifications indicating the last successful retrieval. This allows me to quickly identify issues and respond immediately.
Performance Optimization and Scaling: Keeping Load Under Control
I control the Intervals Based on the size and purpose of the environment: 15 seconds for core systems, 30–60 seconds for less critical servers. By selectively choosing collectors, I reduce metrics and query times. I keep my own text file metrics lean, delete old data, and name them consistently. If the fleet grows significantly, I distribute the load across multiple Prometheus instances and separate responsibilities. The following table shows proven tuning parameters and their effects in production.
| Topic | Setting | Effect | Note |
|---|---|---|---|
| Scrape Interval | 15s / 30s / 60s | Fewer Scrapes Reduce Load | Take criticality by host class into account |
| Collector Selection | Only the required modules | Reduces time series | Document the list for each role |
| Text File Collector | Lean .prom files | Lower parsing costs | Write concisely, use clear terminology |
| Label cardinality | Check labels | Prevents Explosion of the series | Avoid IDs and highly variable values |
| Sharding | Split Prometheus | Scales Scrapes and Queries | Separate Responsibilities |
When it comes to storage systems, I pay particular attention to I/O values and latencies per device and per file system. My guide is a good place to start Monitor Disk Latencies, which summarizes typical symptom chains and metrics. I correlate these values with CPU wait and load to reliably pinpoint bottlenecks. I encapsulate queries in recording rules so that dashboards load quickly. This keeps analysis and operations fast and organized.
Visualization with Grafana: See Clearly, Act Quickly
I use pre-built dashboards for CPU, RAM, disk, network, and systemd, but I customize them to match my labels. An overview dashboard displays status, utilization, and hosts of concern, while detail pages provide a deeper dive. I briefly describe each panel so that everyone understands the meaning of the metrics. Variable selectors speed up switching between hosts or roles. If you’re looking for a complete overview, you’ll find it under Monitoring Stack with Grafana Tips for building a high-performance stack.
Clean packaging and version management: maintaining reproducibility
I ensure that installations are reproducible by explicitly pinning versions and verifying checksums. For fleet setups, I package the Node Exporter as an internal package (e.g., DEB/RPM) with a fixed path structure and system user. I roll out updates in stages and document the version used for each environment. Where appropriate, I store the startup parameters in a EnvironmentFile, so that changes aren't made directly to the unit file and remain properly versioned. I test new releases in staging first before rolling them out widely.
Sample Configuration: systemd Unit and Hardening
I use a simple but robust unit and add curing settings as needed:
[Unit]
Description=Prometheus Node Exporter
After=network-online.target
Wants=network-online.target
[Service]
User=node_exporter
Group=node_exporter
ExecStart=/usr/local/bin/node_exporter \
--web.listen-address=0.0.0.0:9100 \
--collector.systemd \
--collector.processes \
--collector.filesystem.fs-types-exclude='^(tmpfs|devtmpfs|overlay|squashfs)$' \
--collector.filesystem.mount-points-exclude='^/(sys|proc|dev|run)($|/)'
Restart=on-failure
RestartSec=5s
[Install]
WantedBy=multi-user.target
For production hosts, I further harden the service without restricting its read permissions to /proc and /sys to break. I'll set that up as a drop-in (/etc/systemd/system/node_exporter.service.d/hardening.conf) to:
[Service]
NoNewPrivileges=true
PrivateTmp=true
PrivateDevices=true
ProtectSystem=strict
ProtectHome=true
ProtectControlGroups=true
ProtectKernelTunables=true
ProtectKernelModules=true
LockPersonality=true
MemoryDenyWriteExecute=true
CapabilityBoundingSet=
AmbientCapabilities=
RestrictNamespaces=true
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
SystemCallFilter=@system-service
After each change: systemctl daemon-reload and a clean restart. For debugging purposes, I temporarily set a higher log level via --log.level=debug, to view collector and parser details.
Collector Fine-Tuning for Production Environments
I balance visibility and load using targeted filters:
- File systems: I'm excluding pseudo-FS and ephemeral mounts (
--collector.filesystem.fs-types-excludeand--collector.filesystem.mount-points-exclude), to avoid nonsensical series. - Processes:
--collector.processesIt provides useful totals, but generates additional series. I only enable it on hosts where the number of processes provides a signal (e.g., batch or worker nodes). - Network: The
netstat-Collector can generate a large number of labels depending on the kernel and connections. I check the cardinality in staging and, if necessary, disable it selectively. - Pressure/PSI: Modern kernels provide pressure metrics (
--collector.pressure, often enabled by default). I use it to detect CPU, I/O, and memory bottlenecks early on. - NVMe/RAID: Specific collectors (e.g.,.
NVMe) I only enable it where the hardware is available—that way, the dashboards remain meaningful.
I selectively test collectors using the query parameter collect[], without changing the startup parameters. For example: curl 'http://localhost:9100/metrics?collect[]=systemd&collect[]=processes'. That way, I can immediately see the impact of individual collectors.
Text File Collector: Best Practices from Operational Experience
I write metrics atomically: Scripts generate first .tmp-files and replace them at the end using mv. Each file contains only one logical group and is no larger than a few kilobytes. I leave timestamp handling to Prometheus; the files themselves do not need timestamps. If I remove a .prom-file, the associated series will disappear after the next scrape. I document namespaces (e.g.,. business_*) and keep label values stable to control cardinality. Where values fluctuate significantly, I smooth them out in scripts (e.g., by calculating the average) so that dashboards run more smoothly.
Security: Firewall and Proxy Types
I'm starting with network segmentation: The exporter only listens internally, and the firewall only allows traffic from the Prometheus IPs. Here's an example with nftables on a host:
table inet filter {
chain input {
type filter hook input priority 0;
ct state established,related accept
iif lo accept
tcp dport 9100 ip saddr { 10.0.0.10, 10.0.0.11 } accept
tcp dport 9100 drop
}
}
Whenever encryption is required, I set up a local reverse proxy in front of it that handles TLS or mTLS and only forwards to 127.0.0.1:9100 redirects. Alternatively, if the version supports it, I use the exporter's native web configuration via a --web.config.file, so that authentication and certificates remain centrally managed. By default, the service runs without privileges, with minimal permissions for its directory, and writes only where absolutely necessary (e.g., the text file path).
Prometheus Integration in Detail: Relabeling, Limits, Alerts
I keep jobs lean and consistent. A practical job with limits and label maintenance looks like this:
scrape_configs:
- job_name: node
scrape_interval: 30s
scrape_timeout: 10s
sample_limit: 10000
static_configs:
- targets: ['host1:9100','host2:9100']
labels:
env: prod
role: web
relabel_configs:
- source_labels: [__address__]
target_label: instance
regex: '([^:]+)(?::\d+)?'
replacement: '$1'
metric_relabel_configs:
- source_labels: [device]
regex: '^(ram|loop|zram|dm-).*'
action: drop
With metric_relabel_configs I mitigate cardinality by discarding devices that provide little meaningful information. For alarms, I use simple but robust rules:
groups:
- name: node_basic
rules:
- alert: NodeDown
expr: up{job="node"} == 0
for: 5m
labels: {severity: critical}
- alert: HighCPU
expr: 1 - avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) > 0.9
for: 10m
labels: {severity: warning}
To monitor the load, I use scrape_duration_seconds and scrape_samples_scraped per target. This way, I can tell if an additional collector has been activated and is causing the pickup time or serial number to increase disproportionately.
Operation in containers and Kubernetes
I'm setting up the Node Exporter on a host-attached container so that /proc and /sys remain visible from the host. To do this, I mount these paths as read-only in the container and use hostNetwork for consistent ports. In Kubernetes, I run the exporter as a DaemonSet on each node and keep security contexts restrictive (no unnecessary privileges). I take cgroup-v2 environments into account when selecting collectors; important collectors such as meminfo, pressure, filesystem and CPU remain the foundation. I check for deployments using a direct curl Check against the pod to see if the expected host metrics paths are actually being read.
Troubleshooting and quality assurance
- Connectivity: I'm checking
curl -s http://localhost:9100/metrics | headon the target host and, from Prometheus's perspective, reachability via the open port. - Collector's Review: About
collect[]I tested individual collectors without changing the global configuration. - Logs: I'm temporarily raising the log level (
--log.level=debug), to avoid parsing errors or permission issues with/proc//sysvisible. - Version Control: With
node_exporter_build_infoI compare versions and plan upgrades strategically. - A Look at Series: The Metric
scrape_samples_scraped{job="node"}I use this as an estimate of the number of series per host. A sudden increase indicates new collectors or a label explosion. - Timeouts: I think
scrape_timeoutbelowscrape_intervaland watchscrape_timeout_seconds, in order to identify bottlenecks in a timely manner.
Capacity and Storage: Planning Instead of Surprises
I configure data retention in Prometheus based on use cases: short intervals for core systems, longer retention for trends. As the fleet grows, I scale horizontally using sharding (e.g., by location or team) and decouple query and ingest loads. If necessary, I also write metrics to a long-term storage component via remote write. I actively monitor cardinality and consistently clean up unused metrics or labels—especially for text file metrics, which can grow rapidly.
Practical Tips for Diverse Fleets
- IPv6-Only Hosts: I'm listening
[::]:9100and make sure you have the appropriate firewall rules in place. - Specialized Hardware: I enable hardware-specific collectors only where they make sense, and document the differences in role profiles.
- Rolling Updates: I update in batches and monitor the process as I go
up,scrape_duration_secondsandscrape_samples_scraped, in order to detect regressions immediately. - Documentation: I record the actual startup parameters for each roll. This prevents discussions and makes it easier to analyze errors.
In a nutshell: my practice schedule
I'm installing the Node I set up the exporter as its own systemd service, specify the port and listen address, and strictly restrict access. I carefully select the collectors, enter the necessary values via the text file collector, and keep the number of metrics small. In Prometheus, I set appropriate intervals, maintain labels, and define recording rules and alerts for CPU, RAM, disk, and network. I monitor the exporter myself, schedule updates, and regularly check the number of series per host. With clear visualization, I can react faster, identify trends early, and ensure my Linux server monitoring remains reliable in day-to-day operations.


