Last updated: December 1, 2025
OpenTelemetry Host Metrics: The Node Exporter Alternative
When it comes to infrastructure monitoring, the
Host Metrics Receiver
(hostmetrics) is the cornerstone component in an
OpenTelemetry Collector pipeline.
It serves as a replacement for traditional agents (like Prometheus Node Exporter), and allows you to gather critical system metrics like CPU, memory, disk, and network usage, directly from the host where the Collector is running.
Because it needs direct access to the underlying system, this receiver is specifically designed to be used when the Collector is deployed as an Agent (a DaemonSet or a local system service), rather than as a central Gateway.
In this guide, you'll learn how to configure it effectively for real-world production environments.
Quick start: collecting host metrics
To see the receiver in action, let's spin up a Docker Compose setup that uses the Collector to scrape your host's vitals and send that data directly to Prometheus.
You'll need three files. First, create a docker-compose.yaml to orchestrate
the services:
yaml12345678910111213141516171819202122232425262728293031# docker-compose.yamlservices:otelcol:image: otel/opentelemetry-collector-contrib:0.140.0container_name: otelcolvolumes:- ./otelcol.yaml:/etc/otelcol-contrib/config.yaml- /:/hostfs:rorestart: unless-stoppeddepends_on:- prometheusprometheus:image: prom/prometheus:v3.7.3container_name: prometheusrestart: unless-stoppedvolumes:- ./prometheus.yml:/etc/prometheus/prometheus.yml- prometheus_data:/prometheuscommand:- --config.file=/etc/prometheus/prometheus.yml- --storage.tsdb.path=/prometheus- --storage.tsdb.retention.time=15d- --web.enable-lifecycle# Enable Prometheus to accept OTLP writes directly- --web.enable-otlp-receiverports:- 9090:9090volumes:prometheus_data:
There are two critical details here:
-
You must mount the host's root directory (
/)to/hostfsinside the container. Without this, the Collector would only see the statistics of its own tiny container, not your actual server. -
The
--web.enable-otlp-receiverflag turns Prometheus into an OTLP endpoint, allowing the Collector to push data to it via standard HTTP.
Next, create the otelcol.yaml configuration file:
yaml1234567891011121314151617181920212223242526272829303132# otelcol.yamlreceivers:hostmetrics:# Tell the scrapers to look at the mounted volume, not the container's /root_path: /hostfscollection_interval: 1m # Default is 1m, adjust based on resolution needsscrapers:cpu:memory:load:network:disk:filesystem:processors:batch:exporters:debug:verbosity: detailedotlphttp/prometheus:# Push metrics to Prometheus's OTLP receivermetrics_endpoint: http://prometheus:9090/api/v1/otlp/v1/metricstls:insecure: trueservice:pipelines:metrics:receivers: [hostmetrics]processors: [batch]exporters: [otlphttp/prometheus]
The key setting here is root_path: /hostfs which explicitly tells the
hostmetrics receiver to look at the volume we mounted in the previous step,
rather than the container's default filesystem.
Finally, create a minimal prometheus.yml so Prometheus can start:
yaml123# prometheus.ymlglobal:scrape_interval: 10s
Run the command below to launch the services:
bash1docker compose up -d
After a minute, navigate to http://localhost:9090 in your browser. You should
see metrics like system_cpu_time_seconds_total and system_memory_usage_bytes
flowing in from your host.
Understanding hostmetrics scrapers
The hostmetrics receiver acts as a scheduler, but the heavy lifting is done by
scrapers.
These are modular plugins that you enable individually in the scrapers section
of your configuration.
If you are coming from the Prometheus ecosystem, you can think of these scrapers as the equivalent of Node Exporter collectors.
They generate metrics that generally follow OpenTelemetry Semantic Conventions, which may look slightly different from what you are used to seeing in Prometheus.
Here's how the most common scrapers map to the Node Exporter equivalents you might already be familiar with:
| OTel Scraper | Typical Metric Name | Node Exporter Equivalent |
|---|---|---|
| cpu | system.cpu.* | node_cpu_* |
| memory | system.memory.* | node_memory_* |
| load | system.cpu.load_average.1m | node_load1 |
| filesystem | system.filesystem.* | node_filesystem_* |
| disk | system.disk.* | node_disk_* |
| network | system.network.* | node_network_* |
While this covers the most commonly used scrapers, there are a few details worth noting:
-
The default
memoryscraper only looks at physical RAM usage. If you're trying to debug performance issues caused by memory pressure, you need to explicitly enable thepagingscraper to see swap usage and page faults. -
Be very careful with the difference between
processes(plural) andprocess(singular) scrapers:processesis cheap; it simply counts the total number of running or blocked threads on the host.processis heavy; it scrapes detailed CPU and memory metrics for every single executable running on the system. Turning this on without strict filtering through an allowlist is the fastest way to explode your metric cardinality.
-
The
filesystemanddiskscrapers are easy to confuse. The former measures free space and tells you when the disk is full, while the latter measures IOPS and throughput to help you debug slow I/O performance. You usually need both.
Configuring scrapers
Enabling a scraper isn't always a "set it and forget it" action. In production, you often need to tune exactly what they scrape to avoid flooding your backend with useless data.
The most basic thing is controlling the exact metric data points that are generated. Every scraper comes with a set of default metrics (which are on by default) and optional metrics (which are off by default).
You can toggle these using the metrics block within the scraper configuration:
yaml123456789# otelcol.yamlreceivers:hostmetrics:scrapers:filesystem:metrics:# Enable optional metrics that are off by defaultsystem.filesystem.utilization:enabled: true
Reducing noise with filters
Most resource-heavy scrapers (disk, filesystem, network, process)
support using include and exclude rules to restrict data collection to
specific devices or filter out noisy interfaces. These rules can be applied
using exact string matches (strict) or regular expressions (regexp).
For example, to prevent the Network scraper from generating data for local loopback or Docker bridge interfaces, you can exclude them by name:
yaml12345678# otelcol.yamlreceivers:hostmetrics:scrapers:network:exclude:interfaces: ["lo", "docker0"]match_type: strict # Options: strict or regexp
Similarly, the Filesystem scraper can be noisy if it tracks every
specialized system mount. You likely want to filter out tmpfs or overlay
filesystems:
yaml12345678# otelcol.yamlfilesystem:exclude_fs_types:fs_types: ["tmpfs", "autofs", "overlay"]match_type: strictexclude_mount_points:mount_points: ["/var/lib/docker/*"]match_type: regexp
Optimizing metric collection intervals
Not all metrics require the same granularity. You typically need high-resolution polling for volatile resources like CPU and memory to catch transient spikes, while slow-moving metrics like filesystem usage can be checked much less frequently to save resources.
To achieve this, you can define named instances of the receiver to configure
them with independent collection_interval settings and target different sets
of scrapers:
yaml1234567891011121314151617181920receivers:# High-resolution scraping for volatile metricshostmetrics/fast:collection_interval: 10sscrapers:cpu:memory:# Lower-resolution scraping for stable metricshostmetrics/slow:collection_interval: 1mscrapers:filesystem:disk:service:pipelines:metrics:# Both instances feed into the same pipelinereceivers: [hostmetrics/fast, hostmetrics/slow]
Note that if you're running multiple instances of the hostmetrics receiver in
the same Collector, they must all share the same root_path.
Silencing permission errors
The process scraper is particularly sensitive to permissions. It attempts to
read details for every process, including those owned by root or other users.
In a restricted container environment, this may generate a flood of "permission
denied" logs.
You can silence these errors specifically without losing visibility into the processes you can access:
yaml123process:mute_process_user_error: true # Mute "user does not exist" errorsmute_process_io_error: true # Mute "permission denied" on I/O stats
The missing link: resource attributes
One of the biggest "gotchas" with the hostmetrics receiver is that it
generates "naked" metrics. By default, it does not attach critical resource
metadata—like host.name, service.namespace, or cloud.region—to the
telemetry it produces.
Without these attributes, your backend will receive a stream of CPU and memory numbers, but you will have no easy way to distinguish which specific server or environment they belong to.
To solve this, you need to enrich the data before it leaves the collector.
You can manually inject attributes by setting the standard
OTEL_RESOURCE_ATTRIBUTES
environment variable
on your Collector process:
bash1export OTEL_RESOURCE_ATTRIBUTES="service.name=my-payment-service,host.name=prod-server-01"
In production, you should use the resourcedetection processor. It automatically queries the underlying platform APIs to discover and attach the correct metadata.
Refer to our resource processor guide to learn how to manage these attributes effectively.
Final thoughts
The Host Metrics receiver is a workhorse of the OpenTelemetry ecosystem that provides the essential baseline visibility that every service needs.
But raw metrics alone only tell half the story. The true value comes when you can instantly correlate a spike in CPU usage with the specific service, trace, or database query causing it.
At Dash0, we built our platform around this exact concept. Because we're OpenTelemetry-native, we automatically connect your infrastructure metrics to your application traces and logs, giving you a unified view of your system's health from the moment data arrives.
So stop guessing if it's the code or the infrastructure. Try Dash0 today and see the full picture.

