Warum Logs die Platte füllen – und wie Log-Rotation das verhindert

Eine Logdatei schreibt jeder Dienst gern, aber kein Dienst räumt sie von selbst auf. Sie wächst, solange der Dienst läuft. Auf einem Server merkt man das oft erst, wenn die Platte voll ist und plötzlich Datenbank, Webserver oder Backup ausfallen.

Rechenbeispiel: Kleine Meldungen, großes Volumen

Angenommen, drei Dienste laufen ein Jahr lang auf demselben Server, ohne dass jemand die Logs anfasst:

DienstWas wird geloggt?Pro TagNach 365 Tagen
A: Messdienstjede Sekunde eine Zeile (ca. 140 Byte): 86.400 Zeilen/Tagca. 12 MBca. 4,4 GB
B: Anwendung mit Hintergrundjobsalle 10 Sekunden 6 Zeilen (ca. 300 Byte): 51.840 Zeilen/Tagca. 15 MBca. 5,5 GB
C: Dienst mit offenem Portrund 1.000 Zeilen Fehlversuche (Bots probieren Passwörter)ca. 3–4 MBca. 1,3 GB
Summeca. 30 MBca. 11 GB

Auf einer 32-GB-Platte ist damit ein Drittel des Speichers durch Logs belegt, obwohl keine einzige Meldung ein Problem beschreibt. Und: Wer einen Dienst öffentlich erreichbar macht, bekommt Dienst C gratis dazu.

Größe einer Logdatei ≈ Zeilen pro Tag × Bytes pro Zeile × Laufzeit. Schon eine Zeile pro Sekunde sind 12 MB pro Tag.

Wo liegen die Logs?

  • /var/log/ – klassische Textdateien (syslog, auth.log, apache2/, btmp für fehlgeschlagene Logins)
  • journald – das Journal von systemd (journalctl)
  • Docker – pro Container eine Datei /var/lib/docker/containers/<ID>/<ID>-json.log

Die Docker-Falle: Der Standardtreiber json-file hat keine Größenbegrenzung. Ein Container, der ein Jahr durchläuft, hat ein Jahr Logs.

Erst finden, was wächst

df -h                                        # wie voll ist die Platte?
du -xh /var/log --max-depth=1 | sort -h | tail   # größte Ordner unter /var/log
journalctl --disk-usage                      # Größe des Journals
ls -lhS /var/lib/docker/containers/*/*-json.log | head -5   # größte Container-Logs
docker inspect --format '{{.Name}}' <Container-ID>         # welcher Container gehört dazu?

Lösung 1: logrotate für Textdateien

logrotate läuft bei Debian/Ubuntu per systemd-Timer (systemctl list-timers logrotate.timer) und liest /etc/logrotate.conf sowie alle Dateien in /etc/logrotate.d/. Beispiel für einen eigenen Dienst:

/var/log/meinedienst/*.log {
    daily                 # täglich rotieren
    rotate 14             # 14 alte Dateien behalten, ältere löschen
    compress              # alte Dateien mit gzip packen
    delaycompress         # die jüngste alte Datei noch nicht packen
    missingok             # kein Fehler, wenn die Datei fehlt
    notifempty            # leere Logs nicht rotieren
    create 0640 www-data adm
    sharedscripts
    postrotate
        systemctl reload meinedienst >/dev/null 2>&1 || true
    endscript
}
  • Der postrotate-Block sagt dem Dienst, dass er seine Datei neu öffnen soll. Sonst schreibt er weiter in die umbenannte alte Datei.
  • Kann der Dienst das nicht, hilft copytruncate: Die Datei wird kopiert und danach geleert. Dabei können wenige Zeilen verloren gehen.
  • Statt daily geht auch size 50M: rotiert wird erst, wenn die Datei zu groß wird.

Testen, ohne etwas zu verändern:

logrotate -d /etc/logrotate.d/meinedienst   # Trockenlauf mit Ausgabe
logrotate -f /etc/logrotate.d/meinedienst   # Rotation erzwingen

Lösung 2: Journal begrenzen

# /etc/systemd/journald.conf
[Journal]
SystemMaxUse=500M
MaxRetentionSec=1month
systemctl restart systemd-journald
journalctl --vacuum-size=500M     # sofort auf 500 MB verkleinern

Lösung 3: Docker-Logs begrenzen

In der compose.yaml pro Dienst:

services:
  app:
    image: meine-app:1.0
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

Damit belegt der Container höchstens 3 × 10 MB = 30 MB. Für alle neuen Container gilt es global über /etc/docker/daemon.json:

{
  "log-driver": "json-file",
  "log-opts": { "max-size": "10m", "max-file": "3" }
}

Wichtig: Die Einstellung gilt nur für Container, die danach neu erstellt werden (docker compose up -d --force-recreate). Bereits laufende Container behalten ihre alte, unbegrenzte Einstellung.

Lösung 4: An der Ursache ansetzen

Rotation begrenzt den Schaden, aber ein Log, das niemand liest, muss nicht jede Sekunde schreiben. Bei Dienst A aus dem Beispiel landet jeder Messwert ohnehin in einer JSON-Datei. Das Log braucht nur ein Lebenszeichen und Fehler:

n_log += 1
if n_log % 300 == 0:      # bei einer Messung pro Sekunde: alle 5 Minuten
    print(f"sample offset={offset:.6f}", flush=True)
# Fehler werden weiterhin immer geschrieben

Aus 86.400 Zeilen pro Tag werden 288. Bei anderen Diensten reicht oft ein höheres Log-Level (warn statt debug).

Platz sofort freigeben – ohne den Dienst zu zerschießen

truncate -s 0 /var/lib/docker/containers/<ID>/<ID>-json.log   # Datei leeren, nicht löschen
lsof +L1     # gelöschte, aber noch geöffnete Dateien (belegen weiter Platz)

Wer eine große Logdatei mit rm löscht, während der Dienst sie noch offen hat, gibt den Speicher nicht frei. Erst wenn der Dienst neu gestartet wird, verschwindet die Datei wirklich. truncate vermeidet das.

Checkliste für jeden neuen Dienst

  1. Wo schreibt der Dienst hin (Datei, Journal, Docker)?
  2. Wie viele Zeilen pro Tag sind zu erwarten? Kurz hochrechnen.
  3. Rotation und Höchstgröße festlegen, bevor der Dienst produktiv geht.
  4. Einen Alarm oder Cronjob auf df -h einrichten (z. B. Warnung bei 80 % Belegung).

Rotation nachträglich einzuführen ist mühsam. Beim Anlegen des Dienstes sind es zwei Zeilen.