logrotate einrichten: Anleitung mit echten Tests (2026)

logrotate einrichten: Anleitung mit echten Tests (2026)

Wer logrotate einrichten will, hat meistens gerade eine von zwei Erfahrungen gemacht: Entweder ist eine Logdatei auf mehrere Gigabyte gewachsen, oder die Festplatte ist vollgelaufen und das Log war schuld. logrotate ist das Werkzeug, das unter Linux seit Jahrzehnten dafür sorgt, dass Logs regelmäßig umbenannt, komprimiert und irgendwann gelöscht werden. Es ist auf fast jeder Distribution vorinstalliert und läuft still im Hintergrund. Genau das ist seine Stärke und zugleich seine größte Schwäche: Wenn eine Regel falsch ist, merkst du es oft erst, wenn es zu spät ist.

Wir haben für diesen Artikel nicht die Handbuchseite abgeschrieben. Wir haben auf unserem eigenen Ubuntu-Server (24.04, logrotate 3.21.0) nachgesehen, was dort wirklich passiert, und in einem Wegwerf-Ordner rund 30 Tests laufen lassen: mit Programmen, die tausend Zeilen pro Sekunde schreiben, mit absichtlichen Tippfehlern, falschen Rechten und einem echten systemd-Dienst. Einige Ergebnisse haben uns selbst überrascht. Ein einziger Tippfehler hat zum Beispiel dazu geführt, dass logrotate das Log löschte und trotzdem „Erfolg“ meldete.

Ein kreisförmiges Förderband in einem dunklen Serverraum, auf dem Papierrollen laufen, die älteren werden zu leuchtenden Würfeln gepresst

Falls dir Grundbefehle wie ls, du oder tail noch fremd sind, lohnt sich vorher ein Blick in unsere Linux Befehle Liste. Und falls deine Platte gerade schon voll ist, fang mit Linux Festplatte voll an. Dort steht, wie du die Ursache findest. Hier geht es darum, dass es nicht wieder passiert.

logrotate einrichten: die Kurzfassung

Wenn du nur eine eigene Logdatei unter Kontrolle bringen willst, reichen diese fünf Schritte:

  1. Eine Datei in /etc/logrotate.d/ anlegen, zum Beispiel /etc/logrotate.d/meinapp, Rechte 644, Besitzer root.
  2. Eine Regel hineinschreiben, die sagt, welche Datei wie oft rotiert wird, wie viele alte Fassungen bleiben und ob komprimiert wird.
  3. Entscheiden, wie der Dienst vom Wechsel erfährt. Entweder ein Signal oder Neustart in postrotate, oder notfalls copytruncate.
  4. Trocken testen mit sudo logrotate -d /etc/logrotate.d/meinapp, dann einmal echt erzwingen mit sudo logrotate -v -f /etc/logrotate.d/meinapp.
  5. Kontrollieren, ob der Dienst danach in die neue Datei schreibt und nicht in die alte.

Eine solide Vorlage, mit der du starten kannst:

/var/log/meinapp/*.log {
    daily
    rotate 14
    missingok
    notifempty
    compress
    delaycompress
    create 0640 meinapp adm
    sharedscripts
    postrotate
        systemctl reload meinapp.service >/dev/null 2>&1 || true
    endscript
}

Was jede Zeile bedeutet und warum gerade Schritt 3 und 5 so wichtig sind, steht in den folgenden Abschnitten. Wir haben jede der Fallen, die wir beschreiben, selbst nachgestellt.

Was logrotate eigentlich tut

Ein Programm, das loggt, hängt Zeile für Zeile an eine Datei an. Ohne Eingriff wächst diese Datei, bis die Platte voll ist. logrotate löst das mit einem einfachen Prinzip: Es benennt die aktuelle Datei um (app.log wird zu app.log.1), schiebt die älteren eine Nummer weiter (app.log.1 wird zu app.log.2), komprimiert sie auf Wunsch und löscht die älteste, sobald die erlaubte Anzahl überschritten ist. Danach legt es eine neue, leere app.log an.

Wichtig zu verstehen: logrotate ist kein Dienst, der ständig läuft. Es ist ein Programm, das einmal startet, alle Regeln abarbeitet und sich wieder beendet. Ob eine Datei „dran“ ist, entscheidet es anhand einer Zustandsdatei, in der für jedes Log der Zeitpunkt der letzten Rotation steht. Bei Ubuntu und Debian liegt sie unter /var/lib/logrotate/status, bei uns hat sie 41 Einträge.

Fünf durchsichtige Glasbehälter in einer Reihe, jeder etwas kleiner als der vorherige, leuchtende Flüssigkeit fließt von einem zum nächsten

Wann logrotate läuft

Auf modernen Systemen startet ein systemd-Timer logrotate. Bei uns sieht das so aus:

$ systemctl list-timers logrotate.timer
NEXT                        LEFT LAST                        PASSED UNIT
Tue 2026-11-10 00:00:00 UTC  15h Mon 2026-11-09 00:00:00 UTC 8h ago logrotate.timer

Der Timer steht auf OnCalendar=daily mit Persistent=true, also jede Nacht um Mitternacht, und wenn der Server zu dem Zeitpunkt aus war, wird der Lauf beim nächsten Start nachgeholt. Es gibt zusätzlich noch das alte Skript /etc/cron.daily/logrotate. Das beendet sich aber sofort, wenn systemd läuft. Du musst also nicht befürchten, dass logrotate doppelt läuft. Wer mit Cron noch nicht vertraut ist, findet in Cronjob erstellen die Grundlagen, auch zu den cron.daily-Ordnern.

Daraus folgt eine Eigenschaft, die viele übersehen: logrotate prüft nur einmal am Tag. Wenn du size 100M einträgst, wird die Datei nicht beim Erreichen von 100 MB rotiert, sondern beim nächsten Lauf, an dem sie größer als 100 MB ist. Schreibt ein Dienst 5 GB am Tag, wird er trotzdem 5 GB groß. Dazu weiter unten mehr.

Die Hauptkonfiguration

Die Datei /etc/logrotate.conf setzt Standardwerte und bindet den Ordner /etc/logrotate.d/ ein. Ohne Kommentare steht bei uns nur das hier drin:

weekly
su root adm
rotate 4
create
include /etc/logrotate.d

In /etc/logrotate.d/ liegen bei uns 21 Dateien, fast alle von Paketen mitgebracht: nginx, rsyslog, apt, fail2ban, certbot, mariadb, php8.3-fpm, ufw und weitere. Insgesamt prüft logrotate bei jedem Lauf 38 Logs. Die Regel für dich als Admin: Fass die Paketdateien nicht an, sondern leg für eigene Dienste eigene Dateien an. Sonst überschreibt das nächste Paket-Update deine Änderungen oder fragt dich bei jedem Upgrade, welche Fassung du behalten willst.

Der echte Anlass: ein Log, das niemand rotiert

Beim Vorbereiten dieses Artikels haben wir nach großen Logdateien auf unserem Server gesucht:

$ sudo find /var/log -type f -size +20M -printf "%s %p\n" | sort -rn | head -3
297012020 /var/log/imguploader.log
154026061 /var/log/syslog.1
100663296 /var/log/journal/.../system@....journal

Das größte Log mit knapp 300 MB gehört zu einem unserer eigenen kleinen Dienste. Eine Suche in /etc/logrotate.d/ ergab: Für diese Datei gibt es keine Regel. Der Dienst ist ein systemd-Service mit StandardOutput=append:/var/log/imguploader.log, schreibt also jede Anfrage als JSON-Zeile in eine Datei, die seit Monaten wächst. Nichts davon erzeugt eine Warnung. Die Datei wird einfach jeden Tag ein bisschen größer.

Das ist der typische Fall, in dem du logrotate selbst einrichten musst: ein eigener Dienst, ein eigenes Skript, ein Docker-Container mit Bind-Mount oder ein Cronjob mit >> /var/log/irgendwas.log. Pakete bringen ihre Regeln mit. Alles, was du selbst baust, bringt nichts mit.

Wir haben die Produktivdatei für diesen Artikel nicht angefasst. Alle Tests unten liefen in einem Wegwerf-Ordner unter /tmp, mit eigenen Zustandsdateien (-s), damit der echte Zustand von logrotate unberührt bleibt. Die Regel für den Dienst haben wir danach in Ruhe geschrieben, mit den Erkenntnissen aus den Tests.

Eine eigene Regel schreiben, Zeile für Zeile

Eine Regel besteht aus einem oder mehreren Pfaden und einem Block in geschweiften Klammern. Die wichtigsten Anweisungen:

AnweisungWas sie bewirkt
daily / weekly / monthlyWie oft rotiert wird, gemessen an der letzten Rotation
rotate 14Wie viele alte Fassungen behalten werden. 0 heißt: keine
size 100MRotieren, sobald größer als 100 MB, unabhängig vom Zeitplan
maxsize 100MRotieren nach Zeitplan oder früher, wenn größer als 100 MB
minsize 1MNach Zeitplan rotieren, aber nur, wenn mindestens 1 MB groß
missingokKeine Fehlermeldung, wenn die Datei fehlt
notifemptyLeere Dateien nicht rotieren
compressAlte Fassungen komprimieren (Standard: gzip)
delaycompressDie jüngste alte Fassung (.1) noch nicht komprimieren
create 0640 user gruppeNach dem Umbenennen eine neue leere Datei mit diesen Rechten anlegen
copytruncateStatt umbenennen: kopieren und Original auf 0 kürzen
dateextDatum statt Nummer als Endung (app.log-20261109)
maxage 30Alte Fassungen, die älter als 30 Tage sind, löschen
su user gruppeAls dieser Benutzer rotieren (nötig bei unsicheren Ordnerrechten)
sharedscriptsSkripte nur einmal ausführen, auch wenn der Pfad viele Dateien trifft
postrotate … endscriptShell-Befehle nach der Rotation, meist um den Dienst zu benachrichtigen

delaycompress klingt überflüssig, ist aber wichtig. Manche Dienste schreiben nach dem Umbenennen noch ein paar Zeilen in die alte Datei, bis sie das Signal verarbeitet haben. Würde logrotate die sofort komprimieren, gingen diese Zeilen verloren oder die komprimierte Datei wäre unvollständig. Mit delaycompress wird app.log.1 erst beim nächsten Lauf zu app.log.2.gz.

Die wichtigste Entscheidung: Wie erfährt der Dienst vom Wechsel?

Hier entsteht der Großteil aller logrotate-Probleme, und darum haben wir diesen Teil am gründlichsten getestet.

Ein Programm, das eine Logdatei öffnet, bekommt vom Betriebssystem einen sogenannten Dateideskriptor. Der zeigt nicht auf den Namen der Datei, sondern auf die Datei selbst. Wenn logrotate app.log in app.log.1 umbenennt, merkt das Programm davon nichts. Es schreibt weiter in dieselbe Datei, die jetzt nur anders heißt.

Test 1: create ohne Benachrichtigung

Wir haben ein kleines Python-Programm gestartet, das jede Millisekunde eine nummerierte Zeile in svc.log schreibt und die Datei dabei offen hält, wie es fast jeder Dienst tut. Dann haben wir rotiert, nur mit create, ohne postrotate. Das Ergebnis zwei Sekunden später:

-rw-r----- 1 root root     0 svc.log
-rw-r--r-- 1 root root 92143 svc.log.1

$ ls -l /proc/<PID>/fd | grep svc
3 -> /tmp/gmlr/logs/svc.log.1

Die neue svc.log bleibt leer. Das Programm schreibt munter in svc.log.1 weiter. Beim nächsten Lauf wird svc.log.1 zu svc.log.2, und das Programm schreibt dort weiter. Mit compress wird es noch schlimmer: logrotate komprimiert eine Datei, in die gerade geschrieben wird, und löscht dann das Original. Der Dienst schreibt dann in eine gelöschte Datei. Genau so entsteht der klassische Fall, bei dem df eine volle Platte meldet, du aber nichts findet.

Test 2: copytruncate

Für Programme, die man nicht benachrichtigen kann, gibt es copytruncate. logrotate kopiert dann den Inhalt nach app.log.1 und kürzt das Original auf null Byte. Das Programm schreibt einfach weiter in seine Datei, die jetzt wieder klein ist. Klingt elegant. Das Handbuch warnt aber: Zwischen Kopieren und Kürzen gibt es ein kleines Zeitfenster, in dem Zeilen verloren gehen können.

Wir wollten wissen, wie klein dieses Fenster in der Praxis ist. Wir haben mit demselben Programm (eine Zeile pro Millisekunde) acht Rotationen hintereinander durchgeführt und danach gezählt, welche Zeilennummern fehlen:

1858 Zeilen, fehlend 1
1861 Zeilen, fehlend 1
1859 Zeilen, fehlend 1
1860 Zeilen, fehlend 1
1862 Zeilen, fehlend 1
1857 Zeilen, fehlend 1
1862 Zeilen, fehlend 1
1861 Zeilen, fehlend 1

In acht von acht Läufen ging genau eine Zeile verloren, im allerersten Versuch sogar zwei. Bei einer Zeile pro Millisekunde ist das ein kleines Fenster, aber es ist nicht theoretisch, es tritt jedes Mal auf. Bei einem Dienst, der viel schreibt, verlierst du bei jeder Rotation Zeilen, und zwar ausgerechnet die um Mitternacht. Für ein Zugriffslog ist das meist egal. Für ein Audit-Log oder ein Zahlungslog ist es das nicht.

Ein leuchtender Strahl Tropfen, der in ein Glas gegossen wird, während das Glas umgefüllt wird, einige Tropfen fallen daneben

Test 3: copytruncate und Programme ohne Append-Modus

Es gibt eine zweite Falle bei copytruncate, die seltener erwähnt wird. Sie hängt davon ab, wie das Programm die Datei geöffnet hat. Öffnet es sie im Anhängemodus (O_APPEND, in Python open(..., 'a'), in der Shell >>), springt es nach dem Kürzen automatisch an den neuen Dateianfang. Öffnet es sie aber normal zum Schreiben, merkt es sich seine Position. Nach dem Kürzen schreibt es dann an der alten Stelle weiter, zum Beispiel ab Byte 17.000. Davor entsteht ein Loch.

Genau das haben wir gemessen. Ein Programm, das die Datei ohne Append geöffnet hatte, eine Rotation mit copytruncate, danach:

$ stat -c %s w2.log
26833
$ tr -cd '\000' < w2.log | wc -c
17473
$ du -k w2.log
12

17.473 der 26.833 Bytes sind Nullbytes. Die Datei ist laut ls 26 KB groß, belegt laut du aber nur 12 KB, weil das Loch nicht auf der Platte gespeichert wird. Wer so eine Datei mit less öffnet, sieht am Anfang eine lange Reihe ^@, und grep meldet „Binary file matches“ statt der gesuchten Zeilen. Werkzeuge, die Logs einsammeln, verschlucken sich daran ebenfalls.

Bei einem systemd-Dienst mit StandardOutput=append: passiert das nicht. Den Fall haben wir mit einer Wegwerf-Unit nachgestellt: keine Nullbytes, aber auch hier fehlte nach der Rotation eine Zeile. Prüfen kannst du den Modus eines laufenden Prozesses übrigens selbst. In /proc/<PID>/fdinfo/<FD> steht eine Zeile flags:. Ist darin die Stelle für O_APPEND gesetzt (Oktalwert 02000), wird angehängt. Bei unserem echten Dienst steht dort 02102001, er hängt also an.

Test 4: create mit Signal in postrotate

Der saubere Weg: Das Programm bekommt nach dem Umbenennen ein Signal und öffnet daraufhin seine Logdatei neu. Wir haben unserem Testprogramm beigebracht, bei SIGHUP die Datei zu schließen und neu zu öffnen, und es so rotiert:

/tmp/gmlr/logs/s.log {
  rotate 3
  create 0644 root root
  postrotate
    kill -HUP $(cat /tmp/gmlr/w3.pid)
  endscript
}

Ergebnis in drei Läufen: 0 fehlende Zeilen, und der Dateideskriptor zeigt danach auf die neue s.log. Die paar Zeilen, die zwischen Umbenennen und Signal noch geschrieben werden, landen vollständig in s.log.1. Das ist genau der Grund für delaycompress.

Ein kleiner Leuchtturm schickt einen Lichtstrahl zu einer Maschine mit Zahnrädern, die sich einem neuen leeren Glas zuwendet

Test 5: Das falsche Signal tötet den Dienst

Jetzt kommt der Haken. Wir haben dieselbe Regel mit systemctl kill -s HUP auf einen echten systemd-Dienst angewendet, dessen Programm keinen Handler für SIGHUP hat. Ergebnis:

$ systemctl is-active gmlr-test
inactive

Der Dienst war tot. Das Standardverhalten von SIGHUP unter Linux ist: Prozess beenden. Wer also aus einem Forum kill -HUP übernimmt, ohne zu prüfen, ob das Programm das Signal versteht, schaltet seinen Dienst jede Nacht um Mitternacht ab. Ob er wieder hochkommt, hängt dann allein von Restart= in der Unit ab.

Welches Signal richtig ist, steht in der Dokumentation des Programms:

  • nginx: USR1 öffnet die Logs neu. Das Paket macht das über invoke-rc.d nginx rotate.
  • Apache: systemctl reload apache2 oder apachectl graceful.
  • rsyslog: bringt ein eigenes Skript mit (/usr/lib/rsyslog/rsyslog-rotate).
  • PHP-FPM: USR1 öffnet die Logs neu.
  • MySQL/MariaDB: FLUSH LOGS bzw. mysqladmin flush-logs.
  • Eigene Node-, Python- oder Go-Dienste: meistens gar nichts, außer du hast es eingebaut.

Für eigene Dienste ohne Signal-Handler bleiben zwei ehrliche Wege: in postrotate systemctl restart ausführen (das haben wir getestet, danach schreibt der Dienst sauber in die neue Datei, kurz ist er aber weg), oder bewusst copytruncate nehmen und den Verlust einzelner Zeilen hinnehmen. Noch besser ist oft ein dritter Weg: Der Dienst schreibt gar nicht in eine Datei, sondern nach stdout, und systemd gibt das an journald weiter. Dann übernimmt journald mit SystemMaxUse die Begrenzung, und logrotate wird für diesen Dienst überflüssig. Wie man so eine Unit schreibt, zeigt systemd Service erstellen.

Das Video von Better Stack (englisch) zeigt die Grundstruktur einer Regel und den Unterschied zwischen create und copytruncate am Bildschirm. Wer lieber zuschaut als liest, bekommt dort in kurzer Zeit einen guten Überblick.

Regeln sicher testen

logrotate hat zwei Schalter, die du kennen musst, bevor du eine Regel auf einen Produktivserver loslässt.

-d (Debug): Zeigt, was logrotate tun würde, und tut nichts. Ideal, um Syntaxfehler und Pfade zu prüfen:

sudo logrotate -d /etc/logrotate.d/meinapp

-f (Force): Rotiert sofort, auch wenn der Zeitplan noch nicht erreicht ist. Zusammen mit -v siehst du jeden Schritt:

sudo logrotate -v -f /etc/logrotate.d/meinapp

Ein dritter Schalter ist für Experimente Gold wert: -s (eigene Zustandsdatei). Damit verändert dein Test nicht den echten Zustand unter /var/lib/logrotate/status. Alle unsere Tests liefen so.

Der erste Lauf rotiert nicht

Eine Beobachtung, die viele beim ersten Test verwirrt: Wir haben eine frische Regel mit daily und einer neuen Zustandsdatei ausgeführt, ohne -f. Ergebnis:

Creating new state
  Last rotated at 2026-11-09 08:00
  log does not need rotating (log has already been rotated)

Beim ersten Kontakt mit einer Datei trägt logrotate sie nur in die Zustandsdatei ein und tut so, als sei sie gerade rotiert worden. Die erste echte Rotation passiert also frühestens einen Tag (oder eine Woche, ein Monat) später. Das ist kein Fehler, sondern Absicht. Wer aber nach dem Einrichten nachsieht und „es passiert nichts“ feststellt, sucht dann oft an der falschen Stelle. Für einen sofortigen Test brauchst du -f.

Ohne create verschwindet die Datei

In demselben Test hatten wir create weggelassen. Nach -f lag im Ordner nur noch app.log.1.gz, eine app.log gab es nicht mehr. In der globalen /etc/logrotate.conf steht bei Ubuntu zwar create, das gilt auch für Dateien in logrotate.d/. Wer aber eine eigene Konfiguration ohne diese Hauptdatei startet, etwa in einem Container oder mit logrotate meine.conf, hat diesen Standard nicht. Ob ein Dienst die Datei selbst wieder anlegt, ist von Programm zu Programm verschieden.

Fallen, die wir selbst ausgelöst haben

Neben dem Wechsel-Problem gibt es eine Handvoll Konfigurationsfehler, die besonders fies sind, weil logrotate dabei nicht oder kaum meckert. Wir haben jeden davon absichtlich gebaut.

Ein Tippfehler löscht das Log, Exit-Code 0

Wir haben eine Regel mit zwei Tippfehlern geschrieben, so wie sie nachts um drei entstehen:

/tmp/gmlr/logs/a.log {
 rotat 2
 dayly
}

logrotate meldet beide Zeilen als unknown option -- ignoring line und macht weiter. Ohne rotate-Zeile gilt aber der Standardwert, und der ist null. In der ausführlichen Ausgabe steht dann:

rotating pattern: /tmp/gmlr/logs/a.log  forced from command line (no old logs will be kept)
...
removing old log /tmp/gmlr/logs/a.log.1

Danach war der Ordner leer. Die Logdatei wurde umbenannt und sofort gelöscht, eine neue wurde nicht angelegt. Exit-Code: 0. Wer logrotate nur über den Exit-Code oder systemctl status überwacht, sieht einen erfolgreichen Lauf, während die Daten weg sind. Darum: Nach jeder neuen Regel einmal -d laufen lassen und die Ausgabe wirklich lesen. Steht dort „unknown option“ oder „no old logs will be kept“, stimmt etwas nicht.

Unter Ubuntu greift hier zum Glück meist die globale Zeile rotate 4 aus /etc/logrotate.conf, weil die Dateien in logrotate.d/ die Standardwerte von dort erben. Unser Test lief absichtlich ohne die Hauptdatei. Verlass dich aber nicht darauf: Auf anderen Systemen, in Containern oder mit eigenen Aufrufen fehlt dieser Schutz.

Zu offene Rechte: Die Regel wird still ignoriert

Eine Konfigurationsdatei mit den Rechten 0666 (für alle schreibbar):

warning: Potentially dangerous mode on t8.conf: 0666
error: Ignoring t8.conf because it is writable by group or others.

Die Regel wird komplett übersprungen. Das ist aus Sicherheitsgründen richtig, denn logrotate läuft als root und führt die Skripte in der Datei aus. Aber: Exit-Code 0. Im Journal steht eine Fehlermeldung, sonst passiert nichts, und das Log wächst weiter. Neue Dateien in /etc/logrotate.d/ daher immer mit 644 und Besitzer root anlegen.

Unsichere Ordnerrechte: su fehlt

Liegt ein Log in einem Ordner, der für eine andere Gruppe als root schreibbar ist (bei uns: Gruppe adm, Rechte 775), verweigert logrotate die Arbeit:

error: skipping "/tmp/gmlr/unsafe/u.log" because parent directory has insecure
permissions (It's world writable or writable by group which is not "root")
Set "su" directive in config file to tell logrotate which user/group should be
used for rotation.

Hier ist der Exit-Code immerhin 1. Die Lösung steht in der Meldung: su root adm in die Regel schreiben (oder den Benutzer, dem der Ordner gehört). Danach lief die Rotation sauber. Interessant: Gehörte derselbe Ordner der Gruppe root, gab es mit denselben Rechten keine Beschwerde.

/pfad/* statt /pfad/*.log

Ein Muster wie /var/log/meinapp/* trifft auch die bereits rotierten Dateien. In unserem Test entstanden nach zwei Läufen Namen wie a.log.1.1 und b.log.1.1. Mit Kompression würde daraus app.log.1.gz.1.gz, und die Anzahl der Dateien explodiert. Immer auf die Endung einschränken: /var/log/meinapp/*.log.

dateext und zwei Rotationen am selben Tag

dateext ist beliebt, weil app.log-20261109 lesbarer ist als app.log.3. Rotierst du aber zweimal am selben Tag (etwa durch size oder einen manuellen -f), passiert das hier:

error: destination /tmp/gmlr/logs/d.log-20261109 already exists, skipping rotation

Die zweite Rotation fällt aus. Abhilfe schafft ein Datumsformat mit Uhrzeit, zum Beispiel dateformat -%Y%m%d-%H%M%S. Damit lief bei uns die zweite Rotation eine Sekunde später ohne Fehler.

Ein Fehler in postrotate stoppt nichts mehr

Ein postrotate-Skript, das mit Fehler endet (bei uns exit 3), führt zu error running non-shared postrotate script und Exit-Code 1. Die Rotation selbst ist dann aber schon passiert. Die Datei ist umbenannt, die neue angelegt. Wenn das Skript den Dienst benachrichtigen sollte, schreibt der jetzt in die alte Datei, siehe Test 1. Darum sollten Befehle in postrotate robust sein und Fehler sichtbar machen, statt sie mit || true komplett zu verschlucken, wenn die Benachrichtigung wirklich wichtig ist.

Fehlende Datei ohne missingok

Ohne missingok endet eine Regel für eine nicht vorhandene Datei mit stat of ... failed: No such file or directory und Exit-Code 1. Das ist richtig so, kann aber nerven, wenn ein Dienst nur gelegentlich loggt. Faustregel: missingok für Dienste, die nicht immer laufen. Weglassen, wenn eine fehlende Datei ein Alarmzeichen ist.

Nach Größe rotieren: size, maxsize, minsize

Drei Anweisungen, die ähnlich klingen und sich deutlich unterscheiden. Wir haben jede mit einer 2,7 MB großen Datei und einer Grenze von 1 MB getestet:

  • size 1M ersetzt den Zeitplan komplett. Zusammen mit daily wurde die Datei trotzdem nur nach Größe beurteilt. daily war praktisch wirkungslos.
  • maxsize 1M behält den Zeitplan bei und rotiert zusätzlich früher, wenn die Grenze überschritten ist. Bei uns wurde dieselbe Datei zweimal am selben Tag rotiert, weil sie beim zweiten Lauf wieder über 1 MB lag. Das ist meistens das, was man eigentlich will.
  • minsize 1M rotiert nach Zeitplan, aber nur, wenn die Datei groß genug ist. Gut für Logs, die selten wachsen und sonst täglich als Mini-Dateien rotiert würden.

Alle drei werden nur geprüft, wenn logrotate läuft, also einmal täglich. Wenn ein Log wirklich schnell wächst, kannst du einen zusätzlichen Timer anlegen, der logrotate stündlich nur für diese eine Datei startet:

# /etc/systemd/system/logrotate-meinapp.timer
[Timer]
OnCalendar=hourly
Persistent=true

[Install]
WantedBy=timers.target

Dazu eine passende .service-Datei mit ExecStart=/usr/sbin/logrotate /etc/logrotate.d/meinapp. Achtung: Dann gilt die Regel zweimal, einmal im stündlichen und einmal im nächtlichen Lauf. Das ist mit maxsize unproblematisch. Wer die Datei aus der Hauptkonfiguration heraushalten will, legt sie besser unter einem eigenen Pfad ab, etwa /etc/logrotate-hourly.d/.

In diesem Tutorial (englisch, mit Untertiteln in mehreren Sprachen) richtet Waldirio Pinheiro logrotate Schritt für Schritt ein und zeigt dabei auch die Testläufe mit -d und -f im Terminal.

Kompression: gzip, xz oder zstd?

Standardmäßig komprimiert logrotate mit gzip. Über compresscmd, compressext und compressoptions kannst du andere Programme einsetzen. Wir haben die ersten 50 MB unseres echten Syslogs mit den üblichen Kandidaten komprimiert:

VerfahrenErgebnisZeit
gzip -6 (Standard)6,5 MB0,58 s
gzip -96,2 MB1,05 s
zstd -35,8 MB0,12 s
xz -63,7 MB10,5 s
zstd -193,7 MB24,4 s

Logs lassen sich hervorragend komprimieren, gzip schafft bereits ein Verhältnis von fast 8 zu 1. zstd in der Standardstufe ist fast fünfmal so schnell wie gzip und dabei etwas kleiner. xz und zstd auf höchster Stufe halbieren die Größe noch einmal, kosten aber das 20- bis 40-Fache an Zeit. Für die meisten Server ist der Standard völlig in Ordnung. Wer viele Gigabyte Logs pro Tag hat, fährt mit zstd gut:

compress
compresscmd /usr/bin/zstd
compressext .zst
compressoptions -3 -T0
uncompresscmd /usr/bin/unzstd

Bedenke beim Wechsel, dass zgrep und zless nur mit gzip funktionieren. Für zstd gibt es zstdgrep und zstdless.

Wie viele alte Logs behalten?

Die Frage ist weniger technisch als organisatorisch. Ein paar Anhaltspunkte:

  • Für Fehlersuche reichen meist 7 bis 14 Tage. Probleme, die älter sind, findet man in Logs selten noch sinnvoll.
  • Für Zugriffslogs mit IP-Adressen gilt in der EU die DSGVO. Viele Datenschutzbeauftragte halten 7 bis 14 Tage für vertretbar, wenn die Logs der Sicherheit dienen. Länger braucht eine Begründung. Das ist keine Rechtsberatung, aber ein guter Grund, rotate 365 nicht einfach aus einem Forum zu übernehmen.
  • Für Audit- und Buchhaltungsdaten sind Logdateien meist das falsche Werkzeug. Was jahrelang aufbewahrt werden muss, gehört in eine Datenbank oder ein Archiv mit Backup.

Wichtig ist auch, dass rotate zählt, nicht rechnet. weekly mit rotate 4 hält vier Wochen. daily mit rotate 4 hält vier Tage. Wer zusätzlich nach Größe rotiert, bekommt unter Umständen mehrere Fassungen an einem Tag, und dann ist rotate 14 kein Zwei-Wochen-Fenster mehr. Dafür gibt es maxage, das alte Fassungen nach Alter löscht, egal wie viele es sind.

Unsere fertige Regel für den Praxisfall

Zurück zu dem 300-MB-Log vom Anfang. Unser Dienst ist ein systemd-Service mit StandardOutput=append:, hat keinen Signal-Handler und darf nachts kurz neu starten. Aus den Tests ergibt sich diese Regel:

# /etc/logrotate.d/imguploader
/var/log/imguploader.log {
    daily
    maxsize 50M
    rotate 14
    maxage 30
    missingok
    notifempty
    compress
    delaycompress
    create 0640 root adm
    postrotate
        systemctl restart imguploader.service
    endscript
}

Warum so: create plus Neustart, weil copytruncate messbar Zeilen verliert und HUP den Dienst töten würde. maxsize 50M, damit ein Fehler in einer Schleife nicht trotzdem Gigabytes erzeugt. maxage 30 als zweite Grenze, falls doch mal mehrere Rotationen pro Tag passieren. Und vor dem Aktivieren: sudo logrotate -d /etc/logrotate.d/imguploader und die Ausgabe lesen.

Die langfristig bessere Lösung wäre, den Dienst ins Journal schreiben zu lassen statt in eine Datei. Dann begrenzt journald die Größe, und eine Regel weniger muss gepflegt werden.

logrotate überwachen

Weil viele Fehler leise sind, lohnt sich ein kurzer Blick, ob logrotate wirklich arbeitet:

# Lief der letzte Lauf fehlerfrei?
journalctl -u logrotate --since "-3 days"

# Wann wurde welches Log zuletzt rotiert?
sudo cat /var/lib/logrotate/status

# Welche großen Logs hat keine Regel erfasst?
sudo find /var/log -type f -size +100M

Bei uns zeigte die Zustandsdatei zum Beispiel, dass die nginx-Logs seit Februar nicht mehr rotiert wurden. Kein Fehler: Wir nutzen inzwischen Caddy, die nginx-Logs sind leer, und notifempty überspringt leere Dateien. Aber genau solche Stellen solltest du einmal verstehen, bevor du dich auf die Ausgabe verlässt.

Wer seinen Server dauerhaft beobachten will, sollte eine Warnung auf den Füllstand der Platte legen, nicht auf logrotate selbst. Wie das geht, beschreiben wir in Server Monitoring. Eine volle Platte ist das Symptom, das am Ende jeden logrotate-Fehler sichtbar macht.

Zwei Besonderheiten, die du kennen solltest

Die systemd-Unit ist abgeschottet. Bei Ubuntu 24.04 läuft logrotate.service mit PrivateTmp=true und ProtectSystem=full. Das bedeutet: Ein nächtlicher Lauf sieht ein eigenes, leeres /tmp und darf in /usr, /boot und /etc nicht schreiben. Eine Regel für eine Datei unter /tmp wirkt also beim nächtlichen Lauf ins Leere, obwohl sie beim manuellen Test mit sudo logrotate funktioniert. Das haben wir aus der Unit-Datei abgeleitet und nicht eigens nachgestellt. Logs gehören ohnehin nach /var/log.

Docker-Container sind ein eigenes Thema. Was ein Container auf stdout schreibt, landet nicht bei logrotate, sondern beim Log-Treiber von Docker. Dort begrenzt du die Größe mit max-size und max-file in /etc/docker/daemon.json oder pro Container in der Compose-Datei. logrotate brauchst du nur für Dateien, die ein Container über ein Volume auf den Host schreibt.

Ein aufgeräumter Archivraum mit Regalen voller kleiner leuchtender Würfel, ein Roboterarm legt den neuesten Würfel ins Regal

Häufige Fragen zu „logrotate einrichten“

Wie richte ich logrotate für eine eigene Logdatei ein? Lege eine Datei unter /etc/logrotate.d/ an (Rechte 644, Besitzer root), trage den Pfad und einen Block mit daily, rotate, compress, missingok, notifempty und create ein und entscheide, wie der Dienst benachrichtigt wird. Dann mit sudo logrotate -d prüfen und mit sudo logrotate -v -f einmal echt testen.

Wie teste ich logrotate, ohne etwas kaputtzumachen? Mit -d zeigt logrotate nur an, was es tun würde. Mit -s /tmp/teststatus benutzt es eine eigene Zustandsdatei, sodass dein Test den echten Zustand nicht verändert. Für echte Experimente ist ein Wegwerf-Ordner mit Testdateien am sichersten.

Warum rotiert logrotate meine Datei nicht? Die häufigsten Gründe: Die Regel wurde gerade erst angelegt (der erste Lauf trägt die Datei nur ein), die Datei ist leer und notifempty ist gesetzt, die Konfigurationsdatei hat zu offene Rechte und wird ignoriert, oder der Ordner hat unsichere Rechte und su fehlt. sudo logrotate -d /etc/logrotate.d/datei zeigt in fast allen Fällen den Grund.

Was ist der Unterschied zwischen create und copytruncate? Bei create wird die Datei umbenannt und eine neue angelegt. Der Dienst muss danach informiert werden, sonst schreibt er in die alte Datei weiter. Bei copytruncate wird kopiert und das Original geleert. Der Dienst merkt nichts, aber zwischen Kopieren und Leeren gehen Zeilen verloren. In unseren Tests war das bei jeder einzelnen Rotation mindestens eine Zeile.

Warum schreibt mein Dienst nach der Rotation in die .1-Datei? Weil er die Datei offen hält und nicht vom Umbenennen erfährt. Du brauchst in postrotate das passende Signal oder einen Neustart. Prüfen kannst du es mit ls -l /proc/<PID>/fd. Zeigt ein Eintrag auf .1, fehlt die Benachrichtigung.

Kann ich einfach kill -HUP in postrotate schreiben? Nur, wenn das Programm SIGHUP versteht. Bei Programmen ohne passenden Handler beendet SIGHUP den Prozess. In unserem Test war der systemd-Dienst danach inaktiv. Prüfe in der Dokumentation, welches Signal das Programm erwartet, oder nimm einen Neustart.

Wie oft läuft logrotate? Bei Ubuntu und Debian einmal täglich über den systemd-Timer logrotate.timer um Mitternacht. Größenregeln wie size und maxsize werden nur bei diesem Lauf geprüft. Für schneller wachsende Logs kannst du einen eigenen stündlichen Timer anlegen.

Wo sehe ich, wann ein Log zuletzt rotiert wurde? In der Zustandsdatei /var/lib/logrotate/status. Dort steht für jede erfasste Datei Datum und Uhrzeit der letzten Rotation. Fehler eines Laufs findest du mit journalctl -u logrotate.

Brauche ich logrotate, wenn ich journald nutze? Für alles, was ins Journal geht, nicht. journald begrenzt sich selbst über SystemMaxUse in /etc/systemd/journald.conf. logrotate brauchst du für klassische Logdateien, die Programme direkt auf die Platte schreiben, etwa Webserver, Datenbanken oder eigene Skripte.

Welche Kompression ist für Logs am besten? gzip ist der Standard und für die meisten Server völlig ausreichend. Bei uns wurden 50 MB Syslog damit zu 6,5 MB in 0,6 Sekunden. zstd war fast fünfmal so schnell und etwas kleiner. xz spart noch mehr Platz, braucht aber rund 18-mal so lange.

Fazit: logrotate einrichten dauert fünf Minuten, das Prüfen ist der wichtigere Teil

logrotate ist kein kompliziertes Werkzeug. Eine Regel ist schnell geschrieben, und die Vorlage am Anfang dieses Artikels deckt die meisten Fälle ab. Die eigentliche Arbeit steckt in zwei Fragen, die keine Vorlage für dich beantwortet: Wie erfährt mein Dienst vom Wechsel? Und hat logrotate meine Regel wirklich so verstanden, wie ich sie gemeint habe?

Unsere Tests haben gezeigt, warum beide Fragen zählen. copytruncate hat bei jeder Rotation Zeilen verloren und bei Programmen ohne Append-Modus Dateien voller Nullbytes erzeugt. Ein HUP an das falsche Programm hat den Dienst beendet. Ein Tippfehler hat das Log gelöscht, und eine Datei mit zu offenen Rechten wurde ignoriert, beides mit Exit-Code 0. Nichts davon fällt auf, solange du nur auf „Lauf erfolgreich“ schaust.

Darum unser Rat: Jede neue Regel einmal mit -d lesen, einmal mit -f echt laufen lassen und danach mit ls -l /proc/<PID>/fd prüfen, wohin der Dienst schreibt. Und ab und zu find /var/log -size +100M laufen lassen. So haben wir unser eigenes 300-MB-Log gefunden, das seit Monaten niemand rotiert hatte.