Um 2:47 Uhr nachts fing mein Handy an zu leuchten. Nicht der Wecker, sondern das Monitoring: Der Cron-Job, der um 2:45 Uhr das Backup vom Webserver auf die NAS schieben sollte, war tot. rsync hatte abgebrochen. Ich stand im Schlafanzug vor dem Rechner und tat, was ich seit fünfzehn Jahren tat: Ich öffnete /var/log/syslog und suchte die Nadel im Heuhaufen. 500 Zeilen später, nach drei falschen Verdächtigen, hatte ich die Ursache. Die Backup-Partition war voll, und das Backup hatte sich selbst ins Knie geschossen. Heute brauche ich für dieselbe Diagnose einen Befehl und drei Sekunden:

$ journalctl -u backup.service --since '02:45' --until '02:50'
Feb 21 02:45:01 web01 systemd[1]: Started backup.service.
Feb 21 02:45:01 web01 backup.sh[1277]: mounting /mnt/backup
Feb 21 02:45:03 web01 backup.sh[1277]: rsync: [Receiver] write failed on /mnt/backup: No space left on device (28)
Feb 21 02:45:03 web01 backup.sh[1277]: rsync error: some files could not be transferred (code 23)
Feb 21 02:45:03 web01 systemd[1]: backup.service: Main process exited, code=exited, status=23/n/a
Feb 21 02:45:03 web01 systemd[1]: backup.service: Failed with result 'exit-code'.

Genau darum geht es in diesem Artikel: journald, strukturierte Logs und die Frage, ob /var/log überhaupt noch eine Daseinsberechtigung hat.

Warum /var/log/syslog so weh tut

Bevor ich zu den Befehlen komme, kurz die Diagnose, warum klassisches Syslog so weh tut. Ich verwalte rund zwanzig Server, die Hälfte davon Debian, dazu ein paar Ubuntu-Maschinen und einen alten CentOS-Kasten, der nur noch migriert wird. Auf allen lief jahrelang dieselbe Geschichte: /var/log/syslog, /var/log/auth.log, /var/log/kern.log, dazu pro Dienst eine eigene Datei. Und jede Anwendung schreibt ihr eigenes Format. Der Cron-Daemon schreibt Feb 21 03:12:44 web01 CRON[1234]: (root) CMD (backup.sh). rsync schreibt rsync error: some files could not be transferred. Die Firewall schreibt wieder etwas anderes. Du kannst das nicht filtern, du kannst es nur greppen, und grep über eine 40-Megabyte-Datei ist eine Suche, kein Werkzeug.

Dazu kommt die Rotation. logrotate kennt jeder, aber logrotate ist ein Flickenteppich: Jede App braucht ihre eigene Konfiguration, jeder Admin vergisst sie, und irgendwann liegt irgendwo eine Datei, die seit März wächst. Ich habe auf einem Kundenserver mal ein /var/log mit 14 Gigabyte gefunden, weil ein PHP-Skript in eine Logdatei schrieb, die niemandem gehörte. Die Platte war voll, die Website down, und die Ursache stand seit drei Monaten in einer Datei, die nie rotiert wurde. Und dann ist da noch das Boot-Problem: Wenn der Server neu startet und danach etwas nicht mehr läuft, siehst du im Syslog nicht, was vor dem Reboot passiert ist. Die Datei ist weg, die Antwort ist weg.

Was journald anders macht

journald, der Log-Dienst von systemd, löst genau diese drei Probleme. Seit systemd 219 ist er auf praktisch jedem modernen Linux der Standard, ob du es willst oder nicht. Er sammelt alles an einem Ort: Kernel-Meldungen, systemd-Einheiten, Cron, SSH, Docker-Container, dazu die Standardausgabe von Diensten. Das Journal ist eine binäre Datenbank in /var/log/journal, keine Textdateien. Das klingt erstmal wie ein Rückschritt, ist aber der Kern des Ganzen: Weil jede Meldung als strukturierter Datensatz gespeichert wird, kannst du nach Feldern filtern, nicht nach Text. Du fragst nicht „welche Zeile enthält backup?", du fragst „was hat die Einheit backup.service in diesem Boot geloggt?". Das ist ein anderes Denken, und es ist das Denken, das dieser Artikel etablieren will.

journalctl -f: Das Live-Fenster aufs ganze System

Der erste Befehl, den du dir merken solltest, ist journalctl -f. Das ist tail -f für das gesamte System. Ich nutze ihn bei fast jeder Fehlersuche: Dienst neu starten, journalctl -f laufen lassen, und du siehst live, was der Dienst beim Start ausgibt. Bei einem Webserver, der nicht hochkommt, ist das oft schneller als jede Logdatei, weil du keine Datei suchen musst. Ein Beispiel aus der Praxis: ein Django-Dienst, der beim Start eine Datenbankverbindung brauchte und nach zehn Sekunden Timeout aufgab. journalctl -f zeigte mir den Timeout live, während ich die Datenbank startete. Die Reihenfolge der Ereignisse war auf einen Blick klar, ohne dass ich Zeitstempel vergleichen musste.

Zeitfenster mit --since und --until

Der zweite Befehl ist das Zeitfenster. journalctl --since 'today' zeigt alles von heute, journalctl --since '1 hour ago' die letzte Stunde, journalctl --since '2026-10-08 14:00' --until '2026-10-08 15:00' ein exaktes Fenster. Das klingt banal, ist aber der Unterschied zwischen „ich suche" und „ich weiß schon, wo". Für mein nächtliches Backup-Problem: journalctl --since '02:45' --until '02:50'. Fünf Minuten Fenster, zwölf Einträge, Ursache in der zweiten Zeile. Die Timestamps sind übrigens seit systemd 233 standardmäßig lokal, vorher war es UTC. Ein Detail, das schon so manchen Admin verwirrt hat, weil die Logs plötzlich zwei Stunden daneben lagen.

Boot-Trennung mit -b

Der dritte Befehl ist mein heimlicher Favorit: -b, für Boot. Jeder Systemstart bekommt eine eigene Boot-ID, und journalctl -b zeigt nur die Meldungen des aktuellen Boots. journalctl -b -1 das des vorherigen, -b -2 das davor. Warum ist das so wertvoll? Der klassische Fall: Der Server wurde neu gestartet, und danach läuft der Docker-Container nicht mehr. Im Syslog-Zeitalter war die alte Boot-Session weg. Mit journald sagst du journalctl -b -1 -u docker.service und siehst genau, was beim letzten Start schiefgelaufen ist. Auf einem Host, der einmal pro Woche automatisch rebootet, ist das Gold wert. Ich habe damit mal einen Fehler gefunden, der nur nach dem monatlichen Kernel-Update auftrat, weil das alte Modul im vorherigen Boot noch geladen war.

Eine Einheit im Blick mit -u

Der vierte Befehl ist der Filter auf eine Einheit. journalctl -u nginx.service zeigt alles, was nginx betrifft, egal ob systemd den Dienst startet oder der Prozess in die Standardausgabe schreibt. Du kannst mehrere Einheiten kombinieren: journalctl -u nginx.service -u php8.2-fpm.service zeigt beide nebeneinander, chronologisch sortiert. Das ersetzt den alten Tanz mit zwei Logdateien und zwei tail-Prozessen. Und die Kombination mit --since ist die mächtigste Frage, die ein Admin stellen kann: „Was hat diese Einheit in diesem Zeitfenster gemacht?". Genau das ist meine Backup-Diagnose: journalctl -u backup.service --since '02:45' --until '02:50'.

Prioritäten filtern mit -p

Der fünfte Befehl ist der Prioritätsfilter. journald kennt die Syslog-Prioritäten 0 (emerg) bis 7 (debug). journalctl -p err zeigt nur Fehler und Schlimmeres, journalctl -p warning Warnungen und Fehler. Was ich täglich nutze: journalctl -p warning --since 'today' als Morgenroutine. Fünf Sekunden, und ich weiß, ob der Server in der Nacht gehustet hat. Ein einzelner Kernel-Error um 3 Uhr ist oft der Vorbote von Hardware-Problemen, lange bevor sie sichtbar werden.

Der JSON-Moment: 500 Zeilen werden ein Objekt

Jetzt kommen wir zum Teil, der diesen Artikel vom Rest unterscheidet: dem strukturierten Output. Das ist der Moment, in dem die 500-Zeilen-Syslog-Wand zu einem JSON-Objekt wird. Der Befehl heißt journalctl -o json. Für das menschliche Auge gibt es journalctl -o json-pretty, das jede Meldung als lesbares Objekt formatiert:

$ journalctl -u backup.service --since '02:45' -p err -o json-pretty
{
        "PRIORITY" : "3",
        "SYSLOG_IDENTIFIER" : "backup.sh",
        "_PID" : "1277",
        "_BOOT_ID" : "3a9f1c2d7e0b4e6e9f8a1b2c3d4e5f60",
        "_HOSTNAME" : "web01",
        "MESSAGE" : "rsync error: some files could not be transferred (code 23)"
}

Jede Zeile, die vorher ein unstrukturierter Text war, ist jetzt ein Datensatz mit Feldern: PRIORITY als Zahl, SYSLOG_IDENTIFIER als String, __REALTIME_TIMESTAMP als Mikrosekunden seit der Epoche, dazu automatisch gesetzte Felder wie _BOOT_ID, _PID und _HOSTNAME. Du kannst das direkt in jq pipen und nach Feldern greifen. journalctl -o json | jq -r 'select(.PRIORITY == "3") | .MESSAGE' liefert alle Fehlermeldungen als reine Textliste. Kein grep-Pattern für zwanzig verschiedene Formate, sondern eine saubere Abfrage auf ein Feld, das garantiert existiert. Wenn du dir einmal ansehen willst, welche Felder journald pro Eintrag wirklich speichert: Ein einzelnes Objekt aus der Ausgabe in den bitcalc JSON-Formatter werfen, und du siehst die komplette Struktur auf einen Blick.

Vergleichstabelle Syslog vs. journald: Felder, Indexierung, Rotation, Größe, Einbindung in Log-Aggregatoren

Und genau hier beginnt Monitoring. Strukturierte Logs sind die Voraussetzung für automatische Auswertung. Ich habe ein kleines Skript, das einmal pro Minute journalctl -o json --since '1 min ago' abfragt und jeden Datensatz mit einer Priorität kleiner gleich err an das Monitoring schickt. Im Syslog-Zeitalter wäre das ein Regex-Albtraum gewesen. Heute ist es eine Filterbedingung. Und wer seine Logs über mehrere Server hinweg auswerten will, landet früher oder später bei Tools wie Loki, Elasticsearch oder Splunk. Die erwarten strukturierte Daten, und JSON ist ihr Muttersprache. Wer einmal gesehen hat, wie ein Fehler von dreißig Servern als einheitliches JSON-Objekt in einer Suche landet, geht nie wieder zu reinen Textdateien zurück.

Größe und Rotation: Das Journal wächst nicht ewig

Jetzt die Frage, die jeder Admin zuerst stellt: Wächst das Journal endlos? Nein, und das ist der Punkt, an dem journald dem Syslog klar überlegen ist. Die Rotation ist eingebaut, nicht nachgerüstet. Der wichtigste Parameter heißt SystemMaxUse in /etc/systemd/journald.conf. Der Standard sind zehn Prozent der Partition, auf der das Journal liegt. Auf einem Server mit 100-Gigabyte-Root sind das 10 Gigabyte, mehr als genug für Wochen von Logs. Wer weniger will, setzt SystemMaxUse=500M und startet den Dienst neu:

$ journalctl --disk-usage
Archived and active journals take up 812.0M in the file system.
$ journalctl --vacuum-size=200M
Vacuuming done, freed 612.0M of archived journals from /var/log/journal.

Die zwei Befehle, die ich in der Praxis öfter tippe als jeden anderen: journalctl --disk-usage zeigt den aktuellen Verbrauch, journalctl --vacuum-size=200M schrumpft das Journal auf die angegebene Größe, ohne dass du eine einzige Zeile manuell löscht. Und --vacuum-time=30d entfernt alles, was älter als 30 Tage ist. Die alte logrotate-Konfiguration für zwölf Dateien kannst du löschen, journald macht das von allein.

Persistenz: Das Journal überlebt den Reboot

Ein Detail, das viele übersehen: journald hält seine Daten standardmäßig nur im RAM, wenn /var/log/journal nicht existiert. Bei einem Reboot ist dann alles weg. Auf den meisten Distributionen legt der Installer das Verzeichnis an, und systemd setzt Storage=auto, was „persistent, wenn das Verzeichnis existiert" bedeutet. Wer sich nicht sicher ist, prüft das mit ls /var/log/journal und legt es bei Bedarf an: mkdir -p /var/log/journal, dann systemctl restart systemd-journald. Alternativ explizit Storage=persistent in die journald.conf schreiben. Auf den Servern, die ich betreue, ist das Standard, seit ich einmal nach einem Absturz genau die zehn Minuten vor dem Reboot verloren hatte, in denen der Fehler passiert war.

Forwarding: Wenn das alte Syslog weiterleben muss

Manche Teams brauchen weiterhin einen Syslog-Server, weil Monitoring oder Compliance das verlangen. journald kann das: ForwardToSyslog=yes in der journald.conf schickt jede Meldung an den lokalen Syslog-Daemon, der sie wie gewohnt weitertransportiert. Das Journal bleibt dabei die Quelle der Wahrheit, das Syslog wird zum Export-Kanal. Wer zentral sammeln will, hat mit systemd-journal-remote ein natives Protokoll, das strukturierte Daten überträgt, kein Text-Gequetsche. Der Klassiker für große Setups bleibt aber die Pipe: journalctl -o json in ein Aggregations-Tool, weil JSON heute von jeder Pipeline gefressen wird.

Wann /var/log noch gewinnt

Trotz allem gibt es Fälle, in denen /var/log weiterlebt, und ich will nicht so tun, als gäbe es sie nicht. Erstens: Anwendungen, die nur in Dateien schreiben. nginx schreibt sein access.log im eigenen Format, viele Java-Anwendungen loggen über Log4j in Dateien, PHP-FPM hat eigene Slow- und Error-Logs. journald bekommt davon nur das mit, was der Prozess auf die Standardausgabe schreibt, und das ist oft gar nichts. Zweitens: Werkzeuge, die Dateien erwarten. Manche Monitoring-Agenten, Log-Shipper und Forensik-Tools wollen einen Pfad, keinen journalctl-Aufruf. Drittens: Altlasten. Solange ein Dienst sein Logformat selbst bestimmt und niemand es ändern will, bleibt die Datei. Der pragmatische Weg: Diese Dateien mit logrotate klein halten, und alles andere, was systemd startet, läuft über das Journal. Ich habe auf meinen Servern genau zwei logrotate-Dateien übrig, beide für Dienste, die ihre Logs nicht ins Journal schreiben. Der Rest ist Geschichte.

Fazit

Zurück zur Nacht um 2:47. Das Backup-Problem von damals habe ich übrigens mit journalctl -u backup.service --since '02:45' gefunden, in drei Sekunden, im Schlafanzug. Die 500-Zeilen-Wall aus /var/log/syslog existiert für mich nicht mehr. Was bleibt, sind strukturierte Datensätze, die ich nach Einheit, Zeit, Priorität und Boot filtern kann, die sich selbst rotieren, die den Reboot überleben und die sich in JSON exportieren lassen, wenn irgendein Tool sie braucht. journald ist nicht perfekt, und die Textdatei wird nicht verschwinden. Aber für jeden, der heute ein System aufsetzt, ist die Frage nicht mehr, ob er strukturierte Logs nutzt. Die Frage ist, wann er anfängt.