Skip to content

Linux Exporter Role Usage Guide

The linux_exporter role deploys Prometheus node_exporter to Linux hosts, running it as a dedicated node-exp system user instead of root.

This role does not issue or manage TLS certificates itself. Certificate issuance is a separate, deliberate step using playbooks/certificate-authority-issue.yml — the same way every other role in this repo handles certs — rather than something bundled into the install. See the three usage options below.

Requirements

Requirement Details
prometheus.prometheus collection Must be installed on the control node (ansible-galaxy collection install prometheus.prometheus).
certificate_authority role Only needed if issuing a certificate (Options B and C below) — not a dependency of linux_exporter itself.

Tags

A tag must be specified when running this role. Running without --tags or with --tags all is not supported and will result in an error.

Tag Includes Description
install create_user, prepare_filesystem, install_node_exporter, verify_endpoint Runs all tasks required to install node_exporter.
create_user Creates the node-exp system group/user.
prepare_filesystem Creates linux_exporter_config_dir (default /etc/node_exporter). Must be created before a certificate is issued into it or a path under it.
install_node_exporter Installs and starts node_exporter via the prometheus.prometheus.node_exporter collection role.
verify_endpoint Polls the metrics endpoint after install and asserts node_exporter_build_info is present in the response. Included in install — not a separate manual step.

Usage

Certificate issuance, when needed, always happens between create_user/prepare_filesystem and install: node_exporter's own preflight checks require the cert/key to already exist on disk before the upstream role will start the service, and the certificate should be owned by node-exp (created in step 1) so node_exporter can actually read it.

The examples below issue the cert/key into linux_exporter_config_dir (/etc/node_exporter) rather than /etc/ssl/{certs,private}. Confirmed against real RHEL 9 and Ubuntu 24.04 hosts: RHEL doesn't ship /etc/ssl/private at all (/etc/ssl there is a symlink tree into /etc/pki/tls with no private entry), so that path only works on Debian/Ubuntu without an extra manual mkdir first. /etc/node_exporter is created by prepare_filesystem regardless of OS, so it always has somewhere to land.

Option A — HTTP only (no TLS)

1. Create host_vars

# host_vars/<host>.yml
linux_exporter_tls_server_config: {}

2. Create the user and config directory

ansible-playbook --limit=<host> playbooks/deploy-node_exporter.yml --tags create_user,prepare_filesystem --become

3. Install

ansible-playbook --limit=<host> playbooks/deploy-node_exporter.yml --tags install --become

verify_endpoint polls http://localhost:9100/metrics automatically, since linux_exporter_tls_server_config is empty.


Option B — Server-side TLS only (default)

An encrypted listener with no client certificate requirement. This is linux_exporter_tls_server_config's default — no host_vars override is needed as long as the certificate is issued to the default paths (/etc/node_exporter/tls.crt, /etc/node_exporter/tls.priv).

1. Create the user and config directory

ansible-playbook --limit=<host> playbooks/deploy-node_exporter.yml --tags create_user,prepare_filesystem --become

2. Issue the certificate

ansible-playbook playbooks/certificate-authority-issue.yml \
    -l <host> \
    -e @extra_vars/certificate_authority.yml \
    -e certificate_authority_linux_key_path=/etc/node_exporter/tls.priv \
    -e certificate_authority_linux_cert_path=/etc/node_exporter/tls.crt \
    -e certificate_authority_linux_key_owner=node-exp \
    -e certificate_authority_linux_key_group=node-exp \
    -e certificate_authority_linux_cert_owner=node-exp \
    -e certificate_authority_linux_cert_group=node-exp \
    -e 'certificate_authority_cert_sans=[]' \
    -e certificate_authority_cert_include_default_names=true \
    -e certificate_authority_cert_validity_days=3650 \
    --tags generate_key_linux,generate_csr_linux,sign_certificate,write_certificate_linux \
    --become

3. Install

ansible-playbook --limit=<host> playbooks/deploy-node_exporter.yml --tags install --become

Option C — Full mTLS (client certificate required)

1. Create host_vars

# host_vars/<host>.yml
linux_exporter_tls_server_config:
  cert_file: /etc/node_exporter/tls.crt
  key_file: /etc/node_exporter/tls.priv
  client_auth_type: RequireAndVerifyClientCert
  client_ca_file: /etc/node_exporter/ca.crt

2. Create the user and config directory

ansible-playbook --limit=<host> playbooks/deploy-node_exporter.yml --tags create_user,prepare_filesystem --become

3. Issue the server certificate

ansible-playbook playbooks/certificate-authority-issue.yml \
    -l <host> \
    -e @extra_vars/certificate_authority.yml \
    -e certificate_authority_linux_key_path=/etc/node_exporter/tls.priv \
    -e certificate_authority_linux_cert_path=/etc/node_exporter/tls.crt \
    -e certificate_authority_linux_key_owner=node-exp \
    -e certificate_authority_linux_key_group=node-exp \
    -e certificate_authority_linux_cert_owner=node-exp \
    -e certificate_authority_linux_cert_group=node-exp \
    -e 'certificate_authority_cert_sans=[]' \
    -e certificate_authority_cert_include_default_names=true \
    -e certificate_authority_cert_validity_days=3650 \
    --tags generate_key_linux,generate_csr_linux,sign_certificate,write_certificate_linux \
    --become

certificate_authority_linux_key_owner/_key_group/_cert_owner/_cert_group require certificate_authority at or after commit 91de146 ("Add optional owner/group for Linux cert and key files") — older checkouts of that role won't apply ownership, leaving the key root-owned and unreadable by node_exporter.

4. Write the trusted CA bundle

Nothing in certificate_authority's Linux tasks writes the CA bundle to the target — there's no Linux equivalent of trust_root_ca_windows.yml. Copy it directly with an ad hoc command instead:

ansible <host> -m ansible.builtin.copy \
    -a "dest=/etc/node_exporter/ca.crt mode=0644 owner=node-exp group=node-exp content='{{ ca_certificate }}'" \
    -e @extra_vars/certificate_authority.yml \
    --become

5. Install

ansible-playbook --limit=<host> playbooks/deploy-node_exporter.yml --tags install --become

Note: verify_endpoint doesn't supply a client certificate by default, and a RequireAndVerifyClientCert listener will reject an unauthenticated request — so by default verify_endpoint just skips the check under full mTLS (with an explanatory message) rather than failing the install. Set linux_exporter_test_client_cert/linux_exporter_test_client_key to a certificate trusted by client_ca_file if you want verify_endpoint to actually run and pass under full mTLS — see Variables.


Issuing a client certificate (for Prometheus, or for testing)

Under full mTLS, anything that connects to node_exporter — the actual Prometheus server scraping it, or you testing by hand — needs its own client certificate trusted by client_ca_file. Issue one the same way as the server cert, just with certificate_authority_cert_extended_key_usage: [clientAuth] instead of the default [serverAuth]:

ansible-playbook playbooks/certificate-authority-issue.yml \
    -l <prometheus-host> \
    -e @extra_vars/certificate_authority.yml \
    -e certificate_authority_linux_key_path=/etc/prometheus/certs/node-exporter-client.key \
    -e certificate_authority_linux_cert_path=/etc/prometheus/certs/node-exporter-client.crt \
    -e 'certificate_authority_cert_extended_key_usage=["clientAuth"]' \
    -e 'certificate_authority_cert_sans=["DNS:prometheus"]' \
    -e certificate_authority_cert_include_default_names=false \
    -e certificate_authority_cert_validity_days=3650 \
    --tags generate_key_linux,generate_csr_linux,sign_certificate,write_certificate_linux \
    --become

Point Prometheus's scrape_configs tls_config at the resulting cert_file/key_file, plus ca_file set to the same CA bundle written in step 4 above.

Issuing one for yourself, to test by hand: target the control node directly instead of a real inventory host — swap -l <prometheus-host> for -i localhost, -c local:

ansible-playbook playbooks/certificate-authority-issue.yml \
    -i localhost, -c local \
    -e @extra_vars/certificate_authority.yml \
    -e certificate_authority_linux_key_path=/tmp/node-exporter-client.key \
    -e certificate_authority_linux_cert_path=/tmp/node-exporter-client.crt \
    -e 'certificate_authority_cert_extended_key_usage=["clientAuth"]' \
    -e 'certificate_authority_cert_sans=["DNS:ansible-client"]' \
    -e certificate_authority_cert_include_default_names=false \
    -e certificate_authority_cert_validity_days=3650 \
    --tags generate_key_linux,generate_csr_linux,sign_certificate,write_certificate_linux \
    --become

--become is still needed even for a /tmp destination — generate_key_linux installs python3-cryptography via the OS package manager first, which needs root regardless of where the key ends up. That leaves the files root-owned, so reclaim them before using them yourself:

sudo chown "$USER":"$USER" /tmp/node-exporter-client.crt /tmp/node-exporter-client.key

Then test directly against a host (swap in its real IP if this box can't resolve its DNS name):

curl -k --cert /tmp/node-exporter-client.crt --key /tmp/node-exporter-client.key https://<host>:9100/metrics

If this host also runs perimeter_auth, the same recipe (with the same paths) is documented in that role's guide — see docs/perimeter-auth/usage-guide.md, step 5.