Linux Observability
Distro support
Prometheus, node_exporter, and Grafana are portable (static binaries /
cross-platform packages); the systemd units are identical. The differences are
install source and opening the scrape port on the firewall. Body uses
Debian/Ubuntu; the RHEL family (Fedora, RHEL, CentOS Stream, Rocky, Alma,
Oracle) equivalents are in the matrix.
| Concept |
Debian/Ubuntu |
RHEL family |
| Install node_exporter |
apt / release binary |
dnf (EPEL has golang-github-prometheus-node-exporter) / release binary |
| Open scrape port (9100) |
ufw allow 9100/tcp |
firewall-cmd --add-port=9100/tcp --permanent && firewall-cmd --reload |
| Install Telegraf |
InfluxData apt repo / apt install telegraf |
InfluxData yum repo / dnf install telegraf |
| Install Datadog agent |
official script / Datadog apt repo |
official script / Datadog dnf repo |
| Open Telegraf scrape port (9273) |
ufw allow 9273/tcp (restrict to monitor IP) |
firewall-cmd --add-rich-rule ... port=9273 |
| systemd unit / service |
identical |
identical |
| Log forwarding |
rsyslog / journald |
same (journald primary on RHEL) |
| Health endpoint pattern |
identical |
identical |
RHEL-family note: node_exporter is in EPEL on RHEL/Rocky/Alma
(ensure_epel), in main on Fedora, or install the upstream static binary on
either. Open the firewall with firewalld, not ufw.
In sk-* scripts use the common.sh primitives (pkg_install, ensure_epel,
firewall_allow) instead of hardcoding the family. See
linux-bash-scripting and
docs/multi-distro/plan.md.
Use when
- Adding metrics collection, log shipping, or health endpoints to a server.
- Making a host visible to Prometheus or a central logging pipeline.
- Standardizing
/health behavior for operational checks.
Do not use when
- The task is reading local logs only; use
linux-log-management.
- The task is general performance triage without telemetry design work; use
linux-system-monitoring.
Required Inputs
| Artefact |
Source |
Required? |
If absent |
| Telemetry objective and service |
Service owner/SLO |
yes |
Stop before installing an unfocused collector |
| Collector or log destination contract |
Platform owner |
yes |
Produce design only; do not expose a port or ship logs |
| Network, authentication, labels, retention |
Platform/security policy |
for integration |
Mark end-to-end delivery unassessed |
| Change authority and rollback window |
Operations owner |
for mutation |
Limit to read/search inspection and proposed config |
Capability Contract
Read/search access supports current-state assessment. Installing exporters/agents, editing units/config, opening ports, transmitting logs, or reloading services requires explicit authority. Use least-privilege credentials and never expose sensitive health data.
Degraded Mode
Fallback when receiver access is unavailable: validate only the reachable segment and label end-to-end scrape/delivery unassessed. Apply the same limit when network, credentials, root, or binaries are missing; a local listener is not proof of monitoring.
Decision Rules
| Choice |
Action |
Failure avoided |
| Prometheus can pull securely |
Use scrape/exporter model |
Unnecessary push state |
| Logs leave the host |
Define filtering, TLS, backpressure, and retention |
Data leak or disk exhaustion |
| Health dependency is slow/optional |
Keep liveness cheap; separate readiness |
Restart storms |
| Scrape port is required |
Restrict source to collector |
Public telemetry exposure |
Workflow
- Define the operational question, consumer, labels, retention, and access boundary.
- Inspect existing collectors, ports, units, and pipelines before adding components.
- Choose metrics, logs, or health semantics using the decision table; stop on undefined sensitive fields.
- Back up config, validate syntax, and apply the smallest authorised change.
- Verify from the consumer side, including auth, timestamps, labels, and failure state.
- On failure, recover prior config/service state and report which segment remains unassessed.
Evidence Produced
| Artefact |
Acceptance |
| Consumer-side telemetry proof |
Shows target, timestamp, expected metric/log/health value, and labels |
| Configuration evidence |
Contains redacted diff, syntax check, service status, and rollback |
| Coverage statement |
Names monitored components, failure states, retention, and unassessed segments |
Quality standards
- Observability changes must be testable from the receiving system, not only from the host.
- Keep health endpoints cheap, deterministic, and safe to expose.
- Prefer standard, maintainable telemetry patterns over bespoke one-offs.
Anti-patterns
- Declaring success from a local listener. Fix: verify scrape or receipt at the consumer.
- Shipping logs without destination/retention. Fix: define transport, filtering, backpressure, and expiry.
- Making
/health a heavy diagnostic. Fix: keep liveness cheap and separate readiness/dependency detail.
- Opening exporter ports globally. Fix: restrict sources to approved collectors.
- Putting secrets or high-cardinality values in labels. Fix: use bounded non-sensitive dimensions.
- Ignoring pipeline failure behaviour. Fix: test receiver outage, buffering, and recovery.
Outputs
| Artefact |
Consumer |
Acceptance condition |
| Telemetry integration |
Operations |
Consumer receives the intended signal with correct labels/timestamps |
| Operational runbook note |
On-call |
Names failure signal, retention, access, and recovery steps |
| Exposure record |
Security owner |
Documents listener, allowed sources, auth/TLS, and residual risk |
Worked Example
For node_exporter on Rocky Linux, restrict TCP 9100 to the Prometheus source, validate the unit, then prove the target is UP and node_uname_info carries the expected instance label. A successful local curl alone leaves consumer delivery unassessed.
References
This skill is self-contained. Every step below uses standard tools and
released binaries; the body shows Debian/Ubuntu, with RHEL-family equivalents
in the Distro support matrix above. The sk-* scripts in the Optional
fast path section are convenience wrappers — never required.
This skill owns metrics and log shipping — turning a server from a
black box you SSH into occasionally into a first-class citizen of a
monitoring stack.
Complements linux-system-monitoring (local, interactive: top, htop,
iostat) with remote, continuous, aggregated observability (Prometheus
scrape targets, Loki log streams, standard /health endpoints).
Informed by Linux System Administration for the 2020s (observability as
mandatory, not optional).
When to use
- Installing Prometheus
node_exporter on a server.
- Creating a standard
/health endpoint for a web service.
- Forwarding logs from a server to a central collector (Loki, ELK,
CloudWatch).
- Auditing which metrics and logs a server is currently emitting.
When NOT to use
top, htop, iostat for right-now triage → linux-system-monitoring.
- Grepping local log files →
linux-log-management.
- Prometheus/Grafana alerting rules — those live in the monitoring
server's config, not on the managed host.
Standing rules
- Every managed server exposes
node_exporter on 127.0.0.1:9100.
UFW allows scrape only from the monitoring server's IP — never from
0.0.0.0. Exporter runs as its own unprivileged user.
- Every web service has a
/health endpoint that checks at minimum:
database reachable, disk not full, required services running. 200 on
healthy, 503 on unhealthy, JSON body with per-check status.
- Logs ship off the server. Local files are for operator grep, not
long-term storage.
- Metrics have cardinality discipline. No per-request unique IDs as
labels. Ever. Prometheus explodes on high cardinality.
- Public vs internal health endpoints are split. Public
/health
returns minimal info (200/503); internal /health/detail (behind
firewall or auth) returns per-check JSON.
- TLS on the wire for log shipping. Always. No plain-text logs
crossing the network — they contain PII and tokens.
Quick reference — manual commands
Install node_exporter from GitHub release (canonical method)
# 1. Download and verify (check releases: https://github.com/prometheus/node_exporter)
VER=1.8.2
cd /tmp
curl -LO https://github.com/prometheus/node_exporter/releases/download/v${VER}/node_exporter-${VER}.linux-amd64.tar.gz
curl -LO https://github.com/prometheus/node_exporter/releases/download/v${VER}/sha256sums.txt
sha256sum -c sha256sums.txt --ignore-missing 2>&1 | grep OK
# 2. Install
tar xzf node_exporter-${VER}.linux-amd64.tar.gz
sudo install -m 0755 node_exporter-${VER}.linux-amd64/node_exporter /usr/local/bin/
# 3. Unprivileged user
sudo useradd --no-create-home --shell /usr/sbin/nologin node_exp
# 4. Systemd unit
sudo tee /etc/systemd/system/node_exporter.service >/dev/null <<'EOF'
[Unit]
Description=Prometheus node exporter
After=network-online.target
Wants=network-online.target
[Service]
User=node_exp
Group=node_exp
ExecStart=/usr/local/bin/node_exporter --web.listen-address=127.0.0.1:9100
Restart=on-failure
RestartSec=3
ProtectSystem=strict
ProtectHome=yes
NoNewPrivileges=yes
CapabilityBoundingSet=
PrivateTmp=yes
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now node_exporter
sudo systemctl status node_exporter --no-pager
# 5. UFW — allow only from monitoring host
sudo ufw allow from 10.0.0.5 to any port 9100 proto tcp
# 6. Verify
curl -s localhost:9100/metrics | head -20
Full deep-dive (collectors, textfile collector, other exporters,
systemd hardening options, cardinality discipline) — see
references/prometheus-setup.md.
Other telemetry agents (Telegraf, Datadog) — alternatives to node_exporter
node_exporter is the recommended default: OSS, pull-based, minimal attack
surface, scraped by your own Prometheus. Two alternatives exist when you have a
concrete reason to use them:
| Agent |
Model |
Use when |
| node_exporter (default) |
OSS, pull |
You run (or want) your own Prometheus/Grafana stack — keeps the smallest surface. |
| Telegraf (InfluxData) |
OSS, push |
You already run InfluxDB, or want one plugin-rich agent (systemd/SNMP/MySQL inputs) pushing to InfluxDB or a Prometheus /metrics endpoint. Stays inside your network. |
| Datadog agent |
SaaS, push |
Your org pays for Datadog and wants metrics+logs+APM in one managed platform — and accepts that operational data (and logs, which carry PII/secrets) leaves your network to Datadog's cloud. |
Telegraf install (both families, signed repo), inputs
(cpu/mem/disk/net/systemd) + outputs (InfluxDB / Prometheus), Datadog install
via the official script/repo, datadog.yaml API-key config, enabling
integrations, and the privacy/egress consideration of a SaaS agent — all in
references/telemetry-agents.md.
API keys / tokens (Telegraf InfluxDB token, Datadog API key) are never put
in a world-readable config: store them in a 0600/root systemd env file and
inject via the unit — see linux-secrets.
Log forwarding with rsyslog (TLS)
sudo apt install rsyslog-gnutls
sudo mkdir -p /etc/rsyslog.d
# Minimal forwarder config (rsyslog.d/90-forward.conf)
sudo tee /etc/rsyslog.d/90-forward.conf >/dev/null <<'EOF'
$DefaultNetstreamDriver gtls
$DefaultNetstreamDriverCAFile /etc/ssl/certs/loghost-ca.crt
$ActionSendStreamDriverMode 1
$ActionSendStreamDriverAuthMode x509/name
$ActionSendStreamDriverPermittedPeer loghost.internal
$ActionQueueType LinkedList
$ActionQueueFileName fwdRule1
$ActionResumeRetryCount -1
$ActionQueueSaveOnShutdown on
*.* @@loghost.internal:6514
EOF
sudo systemctl restart rsyslog
Full setup with fluent-bit, vector, promtail, and journald forwarding —
see references/log-forwarding.md.
Minimal /health endpoint (Nginx → PHP)
# A /health.php file in your web root:
sudo tee /var/www/html/health.php >/dev/null <<'PHP'
<?php
header('Content-Type: application/json');
$checks = [];
$ok = true;
// DB reachable
try {
$pdo = new PDO('mysql:host=127.0.0.1;dbname=app', 'health', getenv('HEALTH_PASS'));
$pdo->query('SELECT 1');
$checks['db'] = 'ok';
} catch (Throwable $e) {
$checks['db'] = 'fail';
$ok = false;
}
// Disk < 90% on / and /var
foreach (['/', '/var'] as $mount) {
$pct = 100 - (disk_free_space($mount) / disk_total_space($mount) * 100);
$checks["disk:$mount"] = $pct < 90 ? 'ok' : 'fail';
if ($pct >= 90) $ok = false;
}
http_response_code($ok ? 200 : 503);
echo json_encode(['status' => $ok ? 'ok' : 'fail', 'checks' => $checks]);
PHP
Full pattern (Nginx-only health via stub_status, Node.js variant,
caching, auth for detail endpoint) — see
references/health-endpoint-pattern.md.
Typical workflows
Workflow: "Make this server visible to Prometheus"
- Install node_exporter (command sequence above).
sudo ufw allow from <monitor-ip> to any port 9100 proto tcp.
curl -s localhost:9100/metrics | head -20 — confirm metrics.
- Add the server as a scrape target on your Prometheus server (outside
the scope of this skill — that lives on the monitoring host).
Workflow: "Add /health to an existing vhost"
- Write the
health.php (or Node.js equivalent) above.
- In the Nginx vhost, add:
location = /health {
include fastcgi_params;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
fastcgi_param SCRIPT_FILENAME /var/www/html/health.php;
access_log off;
}
sudo nginx -t && sudo systemctl reload nginx.
- Test:
curl -sI https://example.com/health → 200 or 503.
Workflow: "Forward logs to a central Loki collector"
- Install fluent-bit (
apt install fluent-bit or upstream repo for
newer versions).
- Drop
/etc/fluent-bit/fluent-bit.conf from
references/log-forwarding.md.
sudo systemctl enable --now fluent-bit.
- Verify arrival at the Loki server.
Troubleshooting / gotchas
node_exporter listening on 0.0.0.0 by accident. The systemd unit
above pins 127.0.0.1:9100 — verify with ss -tlnp | grep 9100. If
it's bound everywhere, fix the --web.listen-address flag and reload.
- UFW allows the scrape but Prometheus can't reach it. Check that
the rule is
from <monitoring-ip> not to <monitoring-ip>. Easy
direction confusion.
- High-cardinality metric explodes Prometheus. Signs: Prometheus OOMs
after adding a new exporter. Usually a label with request IDs or user
IDs. Drop the offending metric or relabel it away.
/health page cached by CDN. Set Cache-Control: no-store in the
Nginx location. A cached 200 will hide a real 503 from the load
balancer for minutes.
- rsyslog forwarding queues on disk fill /var/spool. Set a max queue
size on the
$ActionQueue* config, and monitor /var/spool/rsyslog.
- Fluent-bit does not automatically rotate its own logs. Configure
its log target carefully — it's a log shipper that can create logs.
References
references/prometheus-setup.md —
full node_exporter install, other exporters, cardinality discipline,
systemd hardening.
references/telemetry-agents.md —
Telegraf (InfluxData) and Datadog agent as alternatives to node_exporter:
install on both families, Telegraf inputs/outputs (InfluxDB/Prometheus),
Datadog datadog.yaml/API key/integrations, "when to use which", and the
privacy/egress consideration of a SaaS agent.
references/log-forwarding.md —
rsyslog/fluent-bit/vector/promtail with TLS forwarding examples.
references/health-endpoint-pattern.md —
full /health pattern with PHP and Node.js examples, internal detail
endpoint, caching strategy.
- Book: Linux System Administration for the 2020s — observability is
mandatory, not optional.
- Man pages:
rsyslogd(8), systemd.service(5).
Optional fast path (when sk-* scripts are installed)
Running sudo install-skills-bin linux-observability installs:
| Task |
Fast-path script |
| Install node_exporter as unprivileged systemd service |
sudo sk-node-exporter-install --monitor-ip <ip> |
| Install Telegraf (OSS alternative) with metrics inputs + an output |
sudo sk-telegraf-setup --output prometheus |
Create/verify a /health endpoint for a vhost |
sudo sk-health-endpoint --domain <d> --db mysql |
| Configure log forwarding over TLS |
sudo sk-log-forward-setup --collector <host>:<port> --tls |
These are optional wrappers around the manual steps above.
Scripts
This skill installs the following scripts to /usr/local/bin/. To install:
sudo install-skills-bin linux-observability
| Script |
Source |
Core? |
Purpose |
| sk-node-exporter-install |
scripts/sk-node-exporter-install.sh |
no |
Install Prometheus node_exporter as unprivileged systemd service, firewall-restrict, verify scrape. |
| sk-telegraf-setup |
scripts/sk-telegraf-setup.sh |
no |
Install InfluxData Telegraf (OSS alternative to node_exporter) on both families from the signed repo; write host-metrics inputs (cpu/mem/disk/net/systemd) + one output (Prometheus /metrics or InfluxDB v2); firewall-restrict the scrape port; validate with telegraf --test. Token read from the 0600 env file, never the config. |
| sk-health-endpoint |
scripts/sk-health-endpoint.sh |
no |
Create and verify /health for a vhost: checks db, disk, required services; 200/503 + JSON. |
| sk-log-forward-setup |
scripts/sk-log-forward-setup.sh |
no |
Configure rsyslog or fluent-bit to forward journald and webserver logs to a central collector over TLS. |
1---2name: linux-observability3description: Use when adding or diagnosing Prometheus/node_exporter metrics, central log shipping, or lightweight health endpoints; use linux-system-monitoring for an ad hoc host snapshot and linux-log-management for local log analysis.4license: MIT5---67# Linux Observability89## Distro support1011Prometheus, `node_exporter`, and Grafana are **portable** (static binaries /12cross-platform packages); the systemd units are identical. The differences are13install source and opening the scrape port on the firewall. Body uses14Debian/Ubuntu; the **RHEL family** (Fedora, RHEL, CentOS Stream, Rocky, Alma,15Oracle) equivalents are in the matrix.1617| Concept | Debian/Ubuntu | RHEL family |18|---|---|---|19| Install node_exporter | apt / release binary | dnf (EPEL has `golang-github-prometheus-node-exporter`) / release binary |20| Open scrape port (9100) | `ufw allow 9100/tcp` | `firewall-cmd --add-port=9100/tcp --permanent && firewall-cmd --reload` |21| Install Telegraf | InfluxData apt repo / `apt install telegraf` | InfluxData yum repo / `dnf install telegraf` |22| Install Datadog agent | official script / Datadog apt repo | official script / Datadog dnf repo |23| Open Telegraf scrape port (9273) | `ufw allow 9273/tcp` (restrict to monitor IP) | `firewall-cmd --add-rich-rule ... port=9273` |24| systemd unit / service | identical | identical |25| Log forwarding | rsyslog / journald | same (journald primary on RHEL) |26| Health endpoint pattern | identical | identical |2728**RHEL-family note:** `node_exporter` is in **EPEL** on RHEL/Rocky/Alma29(`ensure_epel`), in main on Fedora, or install the upstream static binary on30either. Open the firewall with firewalld, not ufw.3132In `sk-*` scripts use the `common.sh` primitives (`pkg_install`, `ensure_epel`,33`firewall_allow`) instead of hardcoding the family. See34[`linux-bash-scripting`](../../10-automation-and-scripting/linux-bash-scripting/SKILL.md) and35[`docs/multi-distro/plan.md`](../../docs/multi-distro/plan.md).3637## Use when3839- Adding metrics collection, log shipping, or health endpoints to a server.40- Making a host visible to Prometheus or a central logging pipeline.41- Standardizing `/health` behavior for operational checks.4243## Do not use when4445- The task is reading local logs only; use `linux-log-management`.46- The task is general performance triage without telemetry design work; use `linux-system-monitoring`.4748<!-- dual-compat-start -->4950## Required Inputs5152| Artefact | Source | Required? | If absent |53|---|---|---|---|54| Telemetry objective and service | Service owner/SLO | yes | Stop before installing an unfocused collector |55| Collector or log destination contract | Platform owner | yes | Produce design only; do not expose a port or ship logs |56| Network, authentication, labels, retention | Platform/security policy | for integration | Mark end-to-end delivery unassessed |57| Change authority and rollback window | Operations owner | for mutation | Limit to read/search inspection and proposed config |5859## Capability Contract6061Read/search access supports current-state assessment. Installing exporters/agents, editing units/config, opening ports, transmitting logs, or reloading services requires explicit authority. Use least-privilege credentials and never expose sensitive health data.6263## Degraded Mode6465Fallback when receiver access is unavailable: validate only the reachable segment and label end-to-end scrape/delivery unassessed. Apply the same limit when network, credentials, root, or binaries are missing; a local listener is not proof of monitoring.6667## Decision Rules6869| Choice | Action | Failure avoided |70|---|---|---|71| Prometheus can pull securely | Use scrape/exporter model | Unnecessary push state |72| Logs leave the host | Define filtering, TLS, backpressure, and retention | Data leak or disk exhaustion |73| Health dependency is slow/optional | Keep liveness cheap; separate readiness | Restart storms |74| Scrape port is required | Restrict source to collector | Public telemetry exposure |7576## Workflow77781. Define the operational question, consumer, labels, retention, and access boundary.792. Inspect existing collectors, ports, units, and pipelines before adding components.803. Choose metrics, logs, or health semantics using the decision table; stop on undefined sensitive fields.814. Back up config, validate syntax, and apply the smallest authorised change.825. Verify from the consumer side, including auth, timestamps, labels, and failure state.836. On failure, recover prior config/service state and report which segment remains unassessed.8485## Evidence Produced8687| Artefact | Acceptance |88|---|---|89| Consumer-side telemetry proof | Shows target, timestamp, expected metric/log/health value, and labels |90| Configuration evidence | Contains redacted diff, syntax check, service status, and rollback |91| Coverage statement | Names monitored components, failure states, retention, and unassessed segments |9293## Quality standards9495- Observability changes must be testable from the receiving system, not only from the host.96- Keep health endpoints cheap, deterministic, and safe to expose.97- Prefer standard, maintainable telemetry patterns over bespoke one-offs.9899## Anti-patterns100101- Declaring success from a local listener. Fix: verify scrape or receipt at the consumer.102- Shipping logs without destination/retention. Fix: define transport, filtering, backpressure, and expiry.103- Making `/health` a heavy diagnostic. Fix: keep liveness cheap and separate readiness/dependency detail.104- Opening exporter ports globally. Fix: restrict sources to approved collectors.105- Putting secrets or high-cardinality values in labels. Fix: use bounded non-sensitive dimensions.106- Ignoring pipeline failure behaviour. Fix: test receiver outage, buffering, and recovery.107108## Outputs109110| Artefact | Consumer | Acceptance condition |111|---|---|---|112| Telemetry integration | Operations | Consumer receives the intended signal with correct labels/timestamps |113| Operational runbook note | On-call | Names failure signal, retention, access, and recovery steps |114| Exposure record | Security owner | Documents listener, allowed sources, auth/TLS, and residual risk |115116## Worked Example117118For node_exporter on Rocky Linux, restrict TCP 9100 to the Prometheus source, validate the unit, then prove the target is `UP` and `node_uname_info` carries the expected instance label. A successful local `curl` alone leaves consumer delivery unassessed.119120<!-- dual-compat-end -->121122## References123124- [`../../docs/continuous-improvement/value-stream-5s-qc-story.md`](../../docs/continuous-improvement/value-stream-5s-qc-story.md)125- [`../../docs/continuous-improvement/incident-learning-standard.md`](../../docs/continuous-improvement/incident-learning-standard.md)126127- [`references/prometheus-setup.md`](references/prometheus-setup.md)128- [`references/telemetry-agents.md`](references/telemetry-agents.md)129- [`references/log-forwarding.md`](references/log-forwarding.md)130- [`references/health-endpoint-pattern.md`](references/health-endpoint-pattern.md)131132**This skill is self-contained.** Every step below uses standard tools and133released binaries; the body shows Debian/Ubuntu, with RHEL-family equivalents134in the **Distro support** matrix above. The `sk-*` scripts in the **Optional135fast path** section are convenience wrappers — never required.136137This skill owns **metrics and log shipping** — turning a server from a138black box you SSH into occasionally into a first-class citizen of a139monitoring stack.140141Complements `linux-system-monitoring` (local, interactive: `top`, `htop`,142`iostat`) with remote, continuous, aggregated observability (Prometheus143scrape targets, Loki log streams, standard `/health` endpoints).144145Informed by *Linux System Administration for the 2020s* (observability as146mandatory, not optional).147148---149150## When to use151152- Installing Prometheus `node_exporter` on a server.153- Creating a standard `/health` endpoint for a web service.154- Forwarding logs from a server to a central collector (Loki, ELK,155 CloudWatch).156- Auditing which metrics and logs a server is currently emitting.157158## When NOT to use159160- `top`, `htop`, `iostat` for right-now triage → `linux-system-monitoring`.161- Grepping local log files → `linux-log-management`.162- Prometheus/Grafana *alerting rules* — those live in the monitoring163 server's config, not on the managed host.164165---166167## Standing rules1681691. **Every managed server exposes `node_exporter` on `127.0.0.1:9100`.**170 UFW allows scrape only from the monitoring server's IP — never from171 `0.0.0.0`. Exporter runs as its own unprivileged user.1722. **Every web service has a `/health` endpoint** that checks at minimum:173 database reachable, disk not full, required services running. 200 on174 healthy, 503 on unhealthy, JSON body with per-check status.1753. **Logs ship off the server.** Local files are for operator grep, not176 long-term storage.1774. **Metrics have cardinality discipline.** No per-request unique IDs as178 labels. Ever. Prometheus explodes on high cardinality.1795. **Public vs internal health endpoints are split.** Public `/health`180 returns minimal info (200/503); internal `/health/detail` (behind181 firewall or auth) returns per-check JSON.1826. **TLS on the wire for log shipping.** Always. No plain-text logs183 crossing the network — they contain PII and tokens.184185---186187## Quick reference — manual commands188189### Install node_exporter from GitHub release (canonical method)190191```bash192# 1. Download and verify (check releases: https://github.com/prometheus/node_exporter)193VER=1.8.2194cd /tmp195curl -LO https://github.com/prometheus/node_exporter/releases/download/v${VER}/node_exporter-${VER}.linux-amd64.tar.gz196curl -LO https://github.com/prometheus/node_exporter/releases/download/v${VER}/sha256sums.txt197sha256sum -c sha256sums.txt --ignore-missing 2>&1 | grep OK198199# 2. Install200tar xzf node_exporter-${VER}.linux-amd64.tar.gz201sudo install -m 0755 node_exporter-${VER}.linux-amd64/node_exporter /usr/local/bin/202203# 3. Unprivileged user204sudo useradd --no-create-home --shell /usr/sbin/nologin node_exp205206# 4. Systemd unit207sudo tee /etc/systemd/system/node_exporter.service >/dev/null <<'EOF'208[Unit]209Description=Prometheus node exporter210After=network-online.target211Wants=network-online.target212213[Service]214User=node_exp215Group=node_exp216ExecStart=/usr/local/bin/node_exporter --web.listen-address=127.0.0.1:9100217Restart=on-failure218RestartSec=3219ProtectSystem=strict220ProtectHome=yes221NoNewPrivileges=yes222CapabilityBoundingSet=223PrivateTmp=yes224225[Install]226WantedBy=multi-user.target227EOF228229sudo systemctl daemon-reload230sudo systemctl enable --now node_exporter231sudo systemctl status node_exporter --no-pager232233# 5. UFW — allow only from monitoring host234sudo ufw allow from 10.0.0.5 to any port 9100 proto tcp235236# 6. Verify237curl -s localhost:9100/metrics | head -20238```239240Full deep-dive (collectors, textfile collector, other exporters,241systemd hardening options, cardinality discipline) — see242[`references/prometheus-setup.md`](references/prometheus-setup.md).243244### Other telemetry agents (Telegraf, Datadog) — alternatives to node_exporter245246`node_exporter` is the **recommended default**: OSS, pull-based, minimal attack247surface, scraped by your own Prometheus. Two alternatives exist when you have a248concrete reason to use them:249250| Agent | Model | Use when |251|---|---|---|252| **node_exporter** (default) | OSS, pull | You run (or want) your own Prometheus/Grafana stack — keeps the smallest surface. |253| **Telegraf** (InfluxData) | OSS, push | You already run InfluxDB, or want one plugin-rich agent (systemd/SNMP/MySQL inputs) pushing to InfluxDB or a Prometheus `/metrics` endpoint. Stays inside your network. |254| **Datadog agent** | **SaaS, push** | Your org pays for Datadog and wants metrics+logs+APM in one managed platform — **and accepts that operational data (and logs, which carry PII/secrets) leaves your network** to Datadog's cloud. |255256Telegraf install (both families, signed repo), inputs257(cpu/mem/disk/net/systemd) + outputs (InfluxDB / Prometheus), Datadog install258via the official script/repo, `datadog.yaml` API-key config, enabling259integrations, and the **privacy/egress** consideration of a SaaS agent — all in260[`references/telemetry-agents.md`](references/telemetry-agents.md).261262**API keys / tokens** (Telegraf InfluxDB token, Datadog API key) are never put263in a world-readable config: store them in a `0600`/root systemd env file and264inject via the unit — see [`linux-secrets`](../../02-users-access-and-secrets/linux-secrets/SKILL.md).265266### Log forwarding with rsyslog (TLS)267268```bash269sudo apt install rsyslog-gnutls270sudo mkdir -p /etc/rsyslog.d271272# Minimal forwarder config (rsyslog.d/90-forward.conf)273sudo tee /etc/rsyslog.d/90-forward.conf >/dev/null <<'EOF'274$DefaultNetstreamDriver gtls275$DefaultNetstreamDriverCAFile /etc/ssl/certs/loghost-ca.crt276$ActionSendStreamDriverMode 1277$ActionSendStreamDriverAuthMode x509/name278$ActionSendStreamDriverPermittedPeer loghost.internal279280$ActionQueueType LinkedList281$ActionQueueFileName fwdRule1282$ActionResumeRetryCount -1283$ActionQueueSaveOnShutdown on284285*.* @@loghost.internal:6514286EOF287288sudo systemctl restart rsyslog289```290291Full setup with fluent-bit, vector, promtail, and journald forwarding —292see [`references/log-forwarding.md`](references/log-forwarding.md).293294### Minimal `/health` endpoint (Nginx → PHP)295296```bash297# A /health.php file in your web root:298sudo tee /var/www/html/health.php >/dev/null <<'PHP'299<?php300header('Content-Type: application/json');301$checks = [];302$ok = true;303304// DB reachable305try {306 $pdo = new PDO('mysql:host=127.0.0.1;dbname=app', 'health', getenv('HEALTH_PASS'));307 $pdo->query('SELECT 1');308 $checks['db'] = 'ok';309} catch (Throwable $e) {310 $checks['db'] = 'fail';311 $ok = false;312}313314// Disk < 90% on / and /var315foreach (['/', '/var'] as $mount) {316 $pct = 100 - (disk_free_space($mount) / disk_total_space($mount) * 100);317 $checks["disk:$mount"] = $pct < 90 ? 'ok' : 'fail';318 if ($pct >= 90) $ok = false;319}320321http_response_code($ok ? 200 : 503);322echo json_encode(['status' => $ok ? 'ok' : 'fail', 'checks' => $checks]);323PHP324```325326Full pattern (Nginx-only health via `stub_status`, Node.js variant,327caching, auth for detail endpoint) — see328[`references/health-endpoint-pattern.md`](references/health-endpoint-pattern.md).329330---331332## Typical workflows333334### Workflow: "Make this server visible to Prometheus"3353361. Install node_exporter (command sequence above).3372. `sudo ufw allow from <monitor-ip> to any port 9100 proto tcp`.3383. `curl -s localhost:9100/metrics | head -20` — confirm metrics.3394. Add the server as a scrape target on your Prometheus server (outside340 the scope of this skill — that lives on the monitoring host).341342### Workflow: "Add /health to an existing vhost"3433441. Write the `health.php` (or Node.js equivalent) above.3452. In the Nginx vhost, add:346 ```nginx347 location = /health {348 include fastcgi_params;349 fastcgi_pass unix:/run/php/php8.3-fpm.sock;350 fastcgi_param SCRIPT_FILENAME /var/www/html/health.php;351 access_log off;352 }353 ```3543. `sudo nginx -t && sudo systemctl reload nginx`.3554. Test: `curl -sI https://example.com/health` → 200 or 503.356357### Workflow: "Forward logs to a central Loki collector"3583591. Install fluent-bit (`apt install fluent-bit` or upstream repo for360 newer versions).3612. Drop `/etc/fluent-bit/fluent-bit.conf` from362 [`references/log-forwarding.md`](references/log-forwarding.md).3633. `sudo systemctl enable --now fluent-bit`.3644. Verify arrival at the Loki server.365366---367368## Troubleshooting / gotchas369370- **`node_exporter` listening on 0.0.0.0 by accident.** The systemd unit371 above pins `127.0.0.1:9100` — verify with `ss -tlnp | grep 9100`. If372 it's bound everywhere, fix the `--web.listen-address` flag and reload.373- **UFW allows the scrape but Prometheus can't reach it.** Check that374 the rule is `from <monitoring-ip>` not `to <monitoring-ip>`. Easy375 direction confusion.376- **High-cardinality metric explodes Prometheus.** Signs: Prometheus OOMs377 after adding a new exporter. Usually a label with request IDs or user378 IDs. Drop the offending metric or relabel it away.379- **`/health` page cached by CDN.** Set `Cache-Control: no-store` in the380 Nginx location. A cached 200 will hide a real 503 from the load381 balancer for minutes.382- **rsyslog forwarding queues on disk fill /var/spool.** Set a max queue383 size on the `$ActionQueue*` config, and monitor `/var/spool/rsyslog`.384- **Fluent-bit does not automatically rotate its own logs.** Configure385 its log target carefully — it's a log shipper that can create logs.386387---388389## References390391- [`references/prometheus-setup.md`](references/prometheus-setup.md) —392 full node_exporter install, other exporters, cardinality discipline,393 systemd hardening.394- [`references/telemetry-agents.md`](references/telemetry-agents.md) —395 Telegraf (InfluxData) and Datadog agent as alternatives to node_exporter:396 install on both families, Telegraf inputs/outputs (InfluxDB/Prometheus),397 Datadog `datadog.yaml`/API key/integrations, "when to use which", and the398 privacy/egress consideration of a SaaS agent.399- [`references/log-forwarding.md`](references/log-forwarding.md) —400 rsyslog/fluent-bit/vector/promtail with TLS forwarding examples.401- [`references/health-endpoint-pattern.md`](references/health-endpoint-pattern.md) —402 full `/health` pattern with PHP and Node.js examples, internal detail403 endpoint, caching strategy.404- Book: *Linux System Administration for the 2020s* — observability is405 mandatory, not optional.406- Man pages: `rsyslogd(8)`, `systemd.service(5)`.407408---409410## Optional fast path (when sk-* scripts are installed)411412Running `sudo install-skills-bin linux-observability` installs:413414| Task | Fast-path script |415|---|---|416| Install node_exporter as unprivileged systemd service | `sudo sk-node-exporter-install --monitor-ip <ip>` |417| Install Telegraf (OSS alternative) with metrics inputs + an output | `sudo sk-telegraf-setup --output prometheus` |418| Create/verify a `/health` endpoint for a vhost | `sudo sk-health-endpoint --domain <d> --db mysql` |419| Configure log forwarding over TLS | `sudo sk-log-forward-setup --collector <host>:<port> --tls` |420421These are optional wrappers around the manual steps above.422423## Scripts424425This skill installs the following scripts to `/usr/local/bin/`. To install:426427```bash428sudo install-skills-bin linux-observability429```430431| Script | Source | Core? | Purpose |432|---|---|---|---|433| sk-node-exporter-install | scripts/sk-node-exporter-install.sh | no | Install Prometheus node_exporter as unprivileged systemd service, firewall-restrict, verify scrape. |434| sk-telegraf-setup | scripts/sk-telegraf-setup.sh | no | Install InfluxData Telegraf (OSS alternative to node_exporter) on both families from the signed repo; write host-metrics inputs (cpu/mem/disk/net/systemd) + one output (Prometheus `/metrics` or InfluxDB v2); firewall-restrict the scrape port; validate with `telegraf --test`. Token read from the 0600 env file, never the config. |435| sk-health-endpoint | scripts/sk-health-endpoint.sh | no | Create and verify `/health` for a vhost: checks db, disk, required services; 200/503 + JSON. |436| sk-log-forward-setup | scripts/sk-log-forward-setup.sh | no | Configure rsyslog or fluent-bit to forward journald and webserver logs to a central collector over TLS. |