Skip to main contentSkip to footer

Den Linux-Agenten überwachen

Wie Sie den Agenten aus Zabbix, Nagios, Icinga, Checkmk oder Prometheus überwachen: was eine Prüfung abdecken sollte, das JSON, das status --json ausgibt, die nötigen Rechte, ein fertiges Zabbix-Template und kurze Einrichtungen für die anderen Systeme. Verfügbar ab Version 0.3.40.

Was eine Prüfung abdecken sollte

Der Agent schützt einen Host auf zwei Wegen, die auf verschiedene Weise ausfallen. Die Listen im Kernel blocken weiter, auch wenn der Agent weg ist. Ein gestoppter Agent sieht von außen darum nicht kaputt aus: die Firewall verwirft die bekannten Adressen weiter, und nichts Neues wird erkannt, gesperrt oder gemeldet. Eine brauchbare Prüfung umfasst deshalb mehr als den Prozess.

FrageWo die Antwort stehtGesund
Läuft der watch-Daemon?daemon.running, oder systemctl is-active reportedip-agenttrue, active
Läuft der Sync-Timer?sync.last_attempt_age_sunter 5400 (90 Minuten; ein Host holt je nach Tarif alle 15 oder alle 60 Minuten)
Steht die Kette im Paketpfad?chain_oktrue, auf einem Host, der blockt
Sonst etwas nicht in Ordnung?Exit-Code, status, problems0, ok, leer

Der Daemon schreibt alle 30 Sekunden einen Heartbeat. status hält ihn für tot, wenn dieser Heartbeat älter als zwei Minuten ist. Ein gestoppter Daemon zeigt sich also in der nächsten Prüfung, die mindestens zwei Minuten nach dem Stopp läuft. Sonst ändert sich in status nichts, wenn der Daemon stoppt, und genau dafür gibt es diese Zeile.

status --json

reportedip-agent status --json führt dieselben Prüfungen aus wie status und gibt das Ergebnis als ein JSON-Dokument aus. Der Exit-Code folgt denselben Regeln, mit einem Unterschied: der Textmodus fragt die API live und ist eingeschränkt, wenn das scheitert, --json fragt nicht und ist es aus diesem Grund also nie. Es geht keine Anfrage an die API: der Kontoteil ist die Antwort, die der letzte Sync zwischengespeichert hat, und account.cached_age_s sagt, wie alt sie ist. Eine Prüfung alle paar Minuten kostet darum nichts und kann nicht am Netz hängen bleiben.

json
{
  "schema": 1,
  "status": "degraded",
  "code": 1,
  "problems": [
    { "key": "daemon", "text": "the watch daemon is not running, last heartbeat 6m0s ago" }
  ],
  "version": "v0.3.40",
  "generated_at": "2026-09-29T20:40:00Z",
  "daemon":  { "running": false, "heartbeat_age_s": 361 },
  "update":  { "latest": "0.3.40", "behind": false, "last_check_age_s": 5120, "last_error": "" },
  "backend": "nftables",
  "mode": "drop",
  "chain_ok": true,
  "disk":    { "free_mb": 18234, "min_mb": 200 },
  "sync":    { "running": false, "last_attempt_age_s": 412, "last_ok_age_s": 412 },
  "lists":   { "ssh": { "ipv4": 21873, "ipv6": 1204, "last_ok_age_s": 412, "fails": 0, "last_error": "" } },
  "bans":    { "enabled": true, "kernel": 3, "records": 3, "missing": 0 },
  "sources": { "/var/log/auth.log": { "state": "ok", "last_line_age_s": 12, "hits": 58 } },
  "queue":   { "entries": 0, "max": 5000, "oldest_age_s": -1, "sent_total": 311, "sender_paused": false },
  "account": { "role": "reportedip_professional", "reports_today": 214, "report_limit": 1000,
               "feed": true, "license": "licensed", "reputation_listed": false,
               "group": "", "cached_age_s": 14320 },
  "rules":   { "total": 20, "operator": 0, "disabled": 0, "problems": 0 },
  "health":  {}
}
FeldBedeutung
schemaDie Version dieses Formats. Felder kommen unter derselben Nummer nur hinzu; ein Feld wird nie umbenannt, umgetypt oder entfernt, ohne sie zu erhöhen.
status, codeok und 0, degraded und 1, error und 2. Dasselbe wie der Exit-Code.
problemsEin Eintrag je Grund für degraded, jeweils mit einem kurzen key (daemon, chain, lists, sources, queue, bans, disk, update, license, reputation, backend, state) und einem Satz. Leer, wenn der Host gesund ist.
*_age_sAlter in ganzen Sekunden. -1 heißt nie oder unbekannt, zum Beispiel vor dem ersten Sync.
backendipset, nftables, oder none auf einem Host, der nur meldet. Ein solcher Host hat keine Kette und keinen Sync, chain_ok ist dort also absichtlich false.
lists, sourcesEin Objekt je Liste und je Log-Quelle, mit dem Namen als Schlüssel.
healthDie offenen Zustände, dieselben, um die es in der Zustandsmail geht.
errorNur bei code 2: die Konfiguration oder das State-Verzeichnis ist nicht nutzbar. Das Dokument wird trotzdem ausgegeben, damit die Prüfung einen Wert bekommt und nicht nichts.

Das Dokument enthält nur Zähler, Alter und Zustände: keine Adresse, keinen Key und keine Logzeile. Ein Monitoring-Server kann es also ohne weiteres Nachdenken speichern.

Rechte

status braucht root. Die Konfiguration enthält Ihren API-Key und ist nur für root lesbar, das State-Verzeichnis hat 0700, und die Firewall-Sets lassen sich nur mit root-Rechten lesen. Ein Monitoring-Agent läuft als eigener Benutzer und braucht darum eine sudo-Regel für genau diesen einen Befehl und sonst nichts:

bash
# /etc/sudoers.d/reportedip-monitoring, mode 0440
# Replace zabbix with the user your monitoring agent runs as (nagios, icinga, ...).
zabbix ALL=(root) NOPASSWD: /usr/local/bin/reportedip-agent status --json

# Check the file before it is used, then test as that user:
visudo -cf /etc/sudoers.d/reportedip-monitoring
sudo -u zabbix sudo -n /usr/local/bin/reportedip-agent status --json
Als Monitoring-Benutzer testen, nicht als root. Ein Test als root klappt immer und beweist nichts über die Regel. Nur die letzte Zeile oben, als root ausgeführt, zeigt, was der Monitoring-Agent bekommen wird.

Zabbix

Für Zabbix 7.0 und neuer, mit Zabbix agent oder Zabbix agent 2. Ein UserParameter holt das Dokument alle fünf Minuten, und jedes andere Item nimmt seinen Wert aus diesem einen Aufruf. Der Agent läuft also einmal je Intervall und nicht einmal je Item.

Die Prüfung einrichten

bash
# Zabbix agent 2: /etc/zabbix/zabbix_agent2.d/reportedip.conf
# Zabbix agent:   /etc/zabbix/zabbix_agentd.d/reportedip.conf
UserParameter=reportedip.status,sudo -n /usr/local/bin/reportedip-agent status --json 2>/dev/null || true

# then the sudo rule from the section above, and:
systemctl restart zabbix-agent2    # or zabbix-agent
zabbix_agent2 -t reportedip.status # prints the document

Das || true verhindert, dass ein Host im Zustand degraded zu einem nicht unterstützten Item wird: der Exit-Code steht als code im Dokument, und das Item muss das Dokument in jedem Fall bekommen.

Das Template importieren

Zwei Varianten desselben Templates, je nachdem, wie Ihre Agenten mit dem Server sprechen. Importieren Sie eine davon unter Data collection, Templates, Import und verknüpfen Sie sie mit jedem Host, auf dem der Agent läuft.

Das Template setzt am Status-Item ein Timeout von 20 Sekunden. Ein Agent älter als 7.0 ignoriert das und nimmt sein eigenes Timeout, standardmäßig 3 Sekunden; stellen Sie es auf solchen Hosts in der Agent-Konfiguration auf 20.

Was das Template überwacht

TriggerSchwereLöst aus, wenn
watch daemon is not runningHighdaemon.running false ist oder kein Prozess reportedip-agent watch läuft. Die Prozesszählung braucht keinen UserParameter und funktioniert darum auch, wenn die Statusprüfung es nicht tut.
firewall chain is not in placeHighchain_ok auf einem Host mit Backend false ist.
configuration error, the agent cannot runHighcode 2 ist.
no sync for {$RIP.SYNC.MAXAGE}AverageDie letzte Feed-Anfrage älter ist als das Makro (90 Minuten) oder es keine gab, auf einem Host mit Backend. Der Trigger hängt vom Lizenz-Trigger ab, denn ohne Lizenz geht keine Anfrage hinaus.
no data for {$RIP.NODATA}AverageDas Status-Item 15 Minuten lang nichts bekommen hat.
report queue over {$RIP.QUEUE.PCT}%WarningDie Queue voller ist als das Makro (80).
host has no server licence, feed pausedWarningaccount.license unlicensed ist.
reporting address is listedWarningDie Adresse, von der dieser Host meldet, in der Community-Datenbank steht.
agent degradedWarningJeder andere Grund für code 1. Er hängt von den Triggern darüber ab, ein Problem löst also einen Alarm aus und nicht zwei. Das Item Problems nennt den Grund.
newer agent version availableInfoEs gibt ein neueres Release. Das Selbst-Update installiert es innerhalb von sechs Stunden, der Trigger bleibt also nur offen, wenn der Update-Weg kaputt ist oder auto_update aus ist.

Zwei Discovery-Regeln legen je Liste (IPv4- und IPv6-Einträge, Fehlschläge in Folge) und je Log-Quelle (Zustand, Treffer, Alter der letzten Zeile) eigene Items an. Jede Schwelle ist ein Makro am Template und lässt sich je Host ändern.

Wenn die Items leer bleiben

Value of type "string" is not suitable for value type "Numeric" oder ein Item, das nie einen Wert bekommt, heißt fast immer, dass der Befehl als Zabbix-Benutzer nicht laufen konnte: die sudo-Regel fehlt, hat den falschen Modus (sie muss 0440 haben, sonst ignoriert sudo die Datei) oder nennt einen anderen Pfad. Führen Sie die Testzeile aus dem Abschnitt Rechte als Zabbix-Benutzer aus. Ein aktiver Agent holt seine Item-Liste nur alle RefreshActiveChecks Sekunden, der erste Wert kann nach dem Verknüpfen des Templates also ein paar Minuten dauern.

Nagios und Icinga

Die Exit-Codes von status sind bereits die Plugin-Codes: 0 OK, 1 WARNING, 2 CRITICAL. Ein Plugin sollte aber eine Zeile ausgeben, darum macht ein kurzer Wrapper aus dem Dokument eine Zeile mit Performance-Daten. Er braucht jq.

bash
#!/bin/sh
# /usr/local/lib/nagios/plugins/check_reportedip
out=$(sudo -n /usr/local/bin/reportedip-agent status --json 2>/dev/null)
rc=$?
[ -n "$out" ] || { echo "REPORTEDIP UNKNOWN: no status document, check the sudo rule"; exit 3; }
line=$(printf '%s' "$out" | jq -r '"REPORTEDIP " + (.status | ascii_upcase) + ": "
  + (if (.problems | length) > 0 then ([.problems[].text] | join("; ")) else "daemon running, chain ok" end)
  + " | queue=\(.queue.entries) bans=\(.bans.kernel) sync_age=\(.sync.last_attempt_age_s)s"') || {
  echo "REPORTEDIP UNKNOWN: no status document"; exit 3; }
echo "$line"
[ "$rc" -le 2 ] && exit "$rc" || exit 3
bash
# NRPE: /etc/nagios/nrpe.d/reportedip.cfg
command[check_reportedip]=/usr/local/lib/nagios/plugins/check_reportedip

# Icinga 2 with the agent: a CheckCommand that runs the same script
object CheckCommand "reportedip" {
  command = [ "/usr/local/lib/nagios/plugins/check_reportedip" ]
}

Die sudo-Regel aus dem Abschnitt Rechte gilt auch hier, mit dem Benutzer, unter dem NRPE oder der Icinga-Agent läuft.

Checkmk

Ein Local Check läuft als root im Checkmk-Agenten und braucht darum keine sudo-Regel. Legen Sie das Skript in das Local-Verzeichnis des Agenten und lassen Sie die Services des Hosts einmal neu erkennen.

bash
#!/bin/sh
# /usr/lib/check_mk_agent/local/reportedip, mode 0755
out=$(/usr/local/bin/reportedip-agent status --json 2>/dev/null)
rc=$?
[ -n "$out" ] || { echo "3 ReportedIP_Agent - no status document"; exit 0; }
[ "$rc" -gt 2 ] && rc=3
printf '%s' "$out" | jq -r --argjson rc "$rc" '"\($rc) ReportedIP_Agent queue=\(.queue.entries)|bans=\(.bans.kernel)|sync_age=\(.sync.last_attempt_age_s) "
  + (if (.problems | length) > 0 then ([.problems[].text] | join(", ")) else "daemon running, chain ok" end)' \
  || echo "3 ReportedIP_Agent - no status document"

Prometheus

Der Textfile-Collector des Node Exporters liest Metriken aus Dateien. Ein Timer, der alle fünf Minuten eine Datei schreibt, reicht also. Das Verzeichnis ist das, worauf --collector.textfile.directory auf Ihren Hosts zeigt.

bash
#!/bin/sh
# /usr/local/sbin/reportedip-prom, run as root every 5 minutes (cron or a systemd timer)
dir=/var/lib/prometheus/node-exporter
out=$(/usr/local/bin/reportedip-agent status --json 2>/dev/null)
[ -n "$out" ] || exit 1   # keep the old file; its age raises the alert
printf '%s' "$out" | jq -r '
  "reportedip_status_code \(.code)",
  "reportedip_daemon_running \(if .daemon.running then 1 else 0 end)",
  "reportedip_heartbeat_age_seconds \(.daemon.heartbeat_age_s)",
  "reportedip_sync_attempt_age_seconds \(.sync.last_attempt_age_s)",
  "reportedip_chain_ok \(if .chain_ok then 1 else 0 end)",
  "reportedip_bans_active \(.bans.kernel)",
  "reportedip_queue_entries \(.queue.entries)",
  (.lists | to_entries[] | "reportedip_list_entries{list=\"\(.key)\",family=\"ipv4\"} \(.value.ipv4)",
                           "reportedip_list_entries{list=\"\(.key)\",family=\"ipv6\"} \(.value.ipv6)")
' > "$dir/reportedip.prom.tmp" && mv "$dir/reportedip.prom.tmp" "$dir/reportedip.prom"

Das Umbenennen am Ende ist wichtig: der Collector darf nie eine halb geschriebene Datei lesen. Alarmieren Sie auf reportedip_status_code > 0, auf reportedip_daemon_running == 0 und über node_textfile_mtime_seconds auf das Alter der Datei selbst, das fängt einen stehengebliebenen Timer ab.

Ohne Monitoring-System

Der Agent schreibt Ihnen selbst eine Mail, wenn etwas nicht stimmt, und noch eine, wenn es behoben ist, einmal je Zustand, sofern notify.email gesetzt ist; siehe Konfiguration. Diese Mail kommt aus dem Sync-Lauf. Sie deckt also einen kaputten Feed, eine kaputte Kette, Quelle oder Lizenz ab, kann aber nicht mitteilen, dass der ganze Host ausgefallen ist. Diesen Teil übernimmt eine einfache Prüfung von außen, ein Ping oder eine Prüfung des SSH-Ports. Für einen schnellen Blick von Hand reichen reportedip-agent status und sein Exit-Code.

Zuletzt aktualisiert: · Betreut vom ReportedIP-Team

Security Focused
DSGVO-konform
Made in Germany
Zurück zur Doku