Festplatte voll: Speicherplatz auf dem Linux-Server finden und freigeben

Veröffentlicht am 13 Min. Lesezeit

Wenn df 100 Prozent meldet und du nichts findet: der vollständige Weg von der Messung bis zur Freigabe, für Debian 12 und 13 sowie Ubuntu 22.04 und 24.04.

Ein voller Datenträger meldet sich selten höflich. Meistens fällt zuerst ein Dienst aus: MariaDB schreibt OS error 28 ins Log, nginx antwortet mit 500, ein Backup bricht mit write error: No space left on device ab, und wenn es ganz dumm läuft, kommt niemand mehr per SSH auf die Maschine, weil sshd keine Sitzungsdateien mehr anlegen kann. Dieser Artikel zeigt den vollständigen Weg: messen, finden, freigeben, kontrollieren. Inklusive der beiden Fälle, an denen die meisten Anleitungen aufhören, nämlich wenn df voll meldet und du nichts findet.

Erst messen: welche Partition ist überhaupt voll?

Bevor irgendetwas gelöscht wird, muss klar sein, welches Dateisystem betroffen ist. Ein volles /boot hat völlig andere Ursachen als ein volles /var.

df -h
df -hT -x tmpfs -x devtmpfs -x squashfs

Die zweite Zeile blendet die Pseudodateisysteme aus. Auf Ubuntu ist das besonders nützlich, weil dort jede installierte Snap-Anwendung als eigener squashfs-Loop-Mount auftaucht und die Ausgabe zumüllt. Diese Loop-Mounts sind übrigens immer zu 100 Prozent belegt, das ist normal und kein Problem.

Wichtig ist die Spalte Mounted on. Typische Konstellationen auf Servern:

MountpointÜbliche Ursache, wenn voll
/Logs, Docker, apt-Zwischenspeicher, Anwendungsdaten
/bootalte Kernel und ihre initramfs-Abbilder
/varJournal, rsyslog, Mailqueue, Datenbanken, Docker
/tmpabgebrochene Uploads, Sessions, Build-Reste

Noch ein Detail, das oft für Verwirrung sorgt: ext4 reserviert standardmäßig fünf Prozent der Kapazität für den Benutzer root. Ein Dienst, der als www-data oder mysql läuft, bekommt also schon ein No space left on device, während root in derselben Sekunde noch problemlos schreiben kann. Auf reinen Datenpartitionen (also nicht auf dem Wurzeldateisystem) darf man diese Reserve gefahrlos senken:

tune2fs -m 1 /dev/sdb1

Auf / sollte die Reserve bleiben. Sie ist genau das Polster, mit dem man ein vollgelaufenes System überhaupt noch reparieren kann.

du richtig einsetzen, statt sich zu verlaufen

Der klassische Einstieg ist eine Ebene nach der anderen, immer mit -x:

du -xh --max-depth=1 / | sort -h
du -xh --max-depth=1 /var | sort -h

Das -x ist der wichtigste Schalter im ganzen Artikel. Es hält du innerhalb eines Dateisystems und verhindert, dass der Befehl in /proc, /sys, eingehängte Netzwerkfreigaben oder Backup-Platten abtaucht. Ohne -x läuft die Suche minutenlang und liefert Zahlen, die mit der vollen Partition nichts zu tun haben. sort -h sortiert die menschenlesbaren Größen korrekt, sodass der größte Brocken unten steht.

Wer sich lieber interaktiv durchklickt, installiert ncdu und startet es ebenfalls mit -x:

apt-get install -y ncdu
ncdu -x /

Einzelne große Dateien findet man schneller direkt:

find /var -xdev -type f -size +100M -exec ls -lh {} +

Zwei Stolpersteine führen hier regelmäßig zu falschen Schlüssen. Erstens: du zählt belegte Blöcke, nicht die logische Dateigröße. Bei dünn belegten Dateien (Datenbank-Tablespaces, Abbilder virtueller Festplatten) klaffen beide Werte weit auseinander. Der Vergleich macht das sichtbar:

du -sh /var/log
du --apparent-size -sh /var/log

Zweitens: du zählt harte Verknüpfungen nur einmal. Wer ohne root arbeitet, bekommt zusätzlich Zeilen wie du: cannot read directory '/var/lib/private': Permission denied und damit systematisch zu kleine Summen. Alle Analysen also als root oder mit sudo fahren.

Das systemd-Journal begrenzen

Das Journal ist auf Servern der häufigste stille Speicherfresser. Standardmäßig darf es zehn Prozent des Dateisystems belegen, gedeckelt bei vier Gigabyte, und zusätzlich hält es 15 Prozent des Dateisystems frei. Auf einer 500 Gigabyte großen Platte sind das bis zu vier Gigabyte reines Log.

journalctl --disk-usage

Hier gibt es einen echten Unterschied zwischen den Distributionen, den viele Anleitungen unterschlagen. Ob das Journal überhaupt auf der Platte landet, hängt bei der Voreinstellung Storage=auto allein daran, ob das Verzeichnis /var/log/journal existiert:

ls -d /var/log/journal
ls -d /run/log/journal

Auf minimalen Debian-Installationen und vielen Cloud-Abbildern von Debian 12 und Debian 13 existiert /var/log/journal nicht. Das Journal liegt dann unter /run/log/journal, also im RAM, verschwindet bei jedem Neustart und belastet die Platte gar nicht. Es belastet stattdessen den Arbeitsspeicher. Ubuntu Server 22.04 und 24.04 legen das Verzeichnis dagegen üblicherweise an und protokollieren dauerhaft auf die Platte. Prüfen statt raten.

Sofort Platz schaffen:

journalctl --rotate
journalctl --vacuum-size=200M
journalctl --vacuum-time=7d

Der --rotate-Aufruf davor ist kein Zierrat: Die vacuum-Optionen löschen ausschließlich bereits archivierte Journaldateien, niemals die gerade aktive. Wenn die aktive Datei den größten Teil ausmacht, passiert ohne vorheriges Rotieren scheinbar nichts, und genau darüber stolpern Leser, die den Befehl aus einem Forenbeitrag kopieren.

Dauerhaft begrenzen über eine eigene Datei, damit spätere Paketaktualisierungen nichts überschreiben:

mkdir -p /etc/systemd/journald.conf.d
printf '[Journal]\nSystemMaxUse=200M\nRuntimeMaxUse=50M\n' > /etc/systemd/journald.conf.d/00-size.conf
systemctl restart systemd-journald

Kontrolle, dass es wirklich gewirkt hat: journalctl --disk-usage muss jetzt einen kleineren Wert melden, und df -h muss mehr freien Platz zeigen. Schrumpft die Journalgröße, während df unverändert bleibt, hält ein Prozess noch gelöschte Dateien offen. Dazu weiter unten mehr.

Zweiter Distributionsunterschied: Auf klassischen Server-Installationen von Debian und Ubuntu läuft parallel oft noch rsyslog und schreibt dieselben Meldungen ein zweites Mal nach /var/log/syslog. Auf minimalen Ubuntu-Abbildern (Cloud, Container) fehlt rsyslog dagegen. Prüfen mit ls -l /var/log/syslog. Existiert die Datei und ist sie riesig, ist nicht das Journal das Problem, sondern eine fehlende oder kaputte logrotate-Regel unter /etc/logrotate.d/.

apt-Zwischenspeicher und alte Kernel

Heruntergeladene Pakete bleiben nach der Installation liegen. Auf einem lange laufenden Server sind das schnell mehrere Gigabyte.

du -sh /var/cache/apt
apt-get clean
du -sh /var/lib/apt/lists

apt-get clean leert /var/cache/apt/archives vollständig, apt-get autoclean nur die Pakete, die es in den Quellen nicht mehr gibt. Was clean nicht anfasst, sind die Paketlisten unter /var/lib/apt/lists. Die dürfen bei vielen eingebundenen Quellen mehrere hundert Megabyte erreichen und lassen sich gefahrlos neu aufbauen:

rm -rf /var/lib/apt/lists/*
apt-get update

Die zweite Zeile ist dabei keine Kür, sondern Pflicht, und zwar sofort. Zwischen dem Löschen der Listen und dem nächsten apt-get update kennt apt kein einziges Paket mehr: jedes apt-get install in diesem Zustand bricht mit E: Unable to locate package ... und Exit-Code 100 ab, obwohl das Paket in den Quellen selbstverständlich vorhanden ist.

Der zweite Klassiker sind alte Kernel. Ubuntu installiert über unattended-upgrades laufend neue Kernel, räumt die alten aber nur auf, wenn man es ausdrücklich erlaubt. Jeder Kernel belegt samt initramfs rund 100 bis 150 Megabyte in einem /boot, das oft nur 512 Megabyte bis ein Gigabyte groß ist.

uname -r
dpkg -l 'linux-image-*'
apt-get autoremove --purge

Der laufende Kernel aus uname -r wird dabei niemals entfernt, ebenso wenig der jeweils neueste. Auf Ubuntu sorgt man vor, indem man in /etc/apt/apt.conf.d/50unattended-upgrades die Zeile Unattended-Upgrade::Remove-Unused-Kernel-Packages "true"; setzt. Auf Debian 12 und 13 ist unattended-upgrades nicht standardmäßig aktiv, dort wächst /boot nur, wenn jemand regelmäßig von Hand aktualisiert und nie aufräumt.

Wenn /boot schon voll ist und apt nicht mehr durchläuft

Das ist der Fall, der wirklich weh tut. Typische Meldungen im Wortlaut:

update-initramfs: failed for /boot/initrd.img-6.8.0-60-generic with 1.
dpkg: error processing package linux-image-6.8.0-60-generic (--configure):
 installed linux-image-6.8.0-60-generic package post-installation script subprocess returned error exit status 1
E: Sub-process /usr/bin/dpkg returned an error code (1)

Jetzt ist die Paketdatenbank in einem halbfertigen Zustand, und jeder weitere apt-Aufruf scheitert an derselben Stelle. Der Weg heraus, in dieser Reihenfolge:

  1. uname -r notieren. Diese Version wird unter keinen Umständen angefasst.
  2. ls -lh /boot ausgeben und die älteste, nicht laufende Version heraussuchen.
  3. Nur deren initrd.img-* löschen, nicht die vmlinuz-*. Die initramfs-Datei ist die mit Abstand größte und lässt sich jederzeit neu erzeugen.
  4. apt-get -f install ausführen, damit dpkg die abgebrochene Konfiguration abschließen kann.
  5. Erst danach apt-get autoremove --purge, damit die alten Pakete sauber und samt GRUB-Eintrag verschwinden.
  6. update-grub laufen lassen und die Ausgabe lesen.

Was man nicht tun sollte: mit rm wahllos Kernel-Dateien aus /boot löschen und den Rest ignorieren. dpkg glaubt dann weiterhin, die Pakete seien installiert, GRUB bietet Einträge an, die ins Leere zeigen, und der nächste Neustart landet im GRUB-Rettungsprompt. Wer bereits gelöscht hat, holt die Konsistenz mit apt-get install --reinstall des betroffenen Pakets oder mit dpkg --purge zurück, danach zwingend update-grub.

Kontrolle: df -h /boot zeigt wieder freien Platz, dpkg -l 'linux-image-*' listet nur noch zwei oder drei Einträge mit Status ii, und die Ausgabe von update-grub nennt genau die Kernel, die auch tatsächlich in /boot liegen.

Docker, Snap und Container-Logs

Auf Docker-Hosts liegt die Antwort fast immer in /var/lib/docker. Nicht raten, sondern fragen:

docker system df
docker system df -v

Die ausführliche Variante trennt sauber nach Images, Containern, Volumes und Build-Zwischenspeicher. Danach gezielt aufräumen:

docker image prune -a
docker builder prune
docker system prune -a

Ein Wort zur Vorsicht bei --volumes: Dieser Schalter löscht auch Volumes, an denen kein laufender Container hängt. Wer seine Datenbank in einem benannten Volume betreibt und den Container gerade gestoppt hat, verliert damit die Daten. Ohne aktuelles Backup gehört docker system prune -a --volumes nicht auf einen Produktivserver.

Der unterschätzte Posten sind die Container-Logs unter /var/lib/docker/containers/*/*-json.log. Der Standardtreiber json-file rotiert ohne ausdrückliche Vorgabe überhaupt nicht. Ein gesprächiger Container schreibt so über Monate zweistellige Gigabytebeträge in eine einzige Datei. Gegenmittel in /etc/docker/daemon.json:

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

Danach systemctl restart docker. Achtung, und das steht in kaum einer Anleitung: Die Einstellung gilt nur für neu erstellte Container. Bestehende behalten ihre alte Konfiguration, bis sie einmal neu angelegt werden, bei Compose also über docker compose up -d --force-recreate.

Die Docker-Versionen unterscheiden sich hier deutlich. Debian 12 liefert docker.io 20.10, Debian 13 bringt 26.1, Ubuntu 22.04 und 24.04 liegen inzwischen bei 29.1. Je neuer die Version, desto mehr Platz belegt der BuildKit-Zwischenspeicher, und desto wichtiger ist docker builder prune, das von docker system prune allein nicht immer vollständig geleert wird.

Auf Ubuntu kommt Snap dazu. Alte Revisionen bleiben deaktiviert liegen und belegen weiter Platz:

snap list --all
snap set system refresh.retain=2

Der Wert 2 ist das Minimum, das snapd akzeptiert, kleinere Werte werden mit einer Fehlermeldung abgewiesen. Debian hat kein Snap an Bord, dieser Abschnitt entfällt dort ersatzlos.

df meldet voll, du findet nichts

Jetzt der interessante Fall. df -h zeigt 100 Prozent, die Summe aus du ergibt aber nur die Hälfte. Dafür gibt es genau vier plausible Ursachen.

Gelöschte, aber noch geöffnete Dateien

Das ist mit Abstand der häufigste Grund. Jemand hat rm /var/log/riesig.log ausgeführt, während ein Dienst die Datei noch offen hielt. Der Verzeichniseintrag ist weg, deshalb sieht du nichts mehr. Die Blöcke bleiben belegt, bis der letzte Dateideskriptor geschlossen ist, deshalb sieht df sie weiterhin.

apt-get install -y lsof
lsof +L1

In der Spalte NLINK steht dann eine 0, hinter dem Pfad steht (deleted). Findet lsof nichts, gibt es keine Ausgabe und der Rückgabewert ist 1, das ist kein Fehler. Ohne lsof geht es auch direkt über das Prozessdateisystem:

ls -l /proc/*/fd 2>/dev/null | grep deleted

Wichtig: unbedingt als root ausführen. Als normaler Benutzer sieht man nur die eigenen Deskriptoren und übersieht damit genau die Systemdienste, die in neun von zehn Fällen der Verursacher sind.

Der saubere Weg zur Freigabe ist ein Neustart des Dienstes, zum Beispiel systemctl restart rsyslog. Wenn ein Neustart nicht in Frage kommt, lässt sich die Datei über ihren Deskriptor auf Länge null kürzen. Prozess-ID und Deskriptornummer stammen aus der lsof-Ausgabe:

truncate -s 0 /proc/1234/fd/7

Das gibt die Blöcke sofort frei, der Prozess schreibt danach weiter. Diese Methode ist für reine Logdateien gedacht, die im Anhängemodus geöffnet sind. Auf Datenbankdateien, Abbildern virtueller Maschinen oder allem anderen mit wahlfreiem Zugriff darf man sie niemals anwenden, dort führt sie zu Datenverlust.

Woran man erkennt, dass es geklappt hat: df -h zeigt sofort mehr freien Platz, und lsof +L1 listet den Eintrag nicht mehr. Ein zweiter du-Durchlauf ändert sich dagegen gar nicht, denn dort war die Datei ja schon vorher unsichtbar. Genau das ist auch der Grund, warum ein Neustart des Servers das Problem scheinbar auf magische Weise löst.

Dateien, die unter einem Mountpoint verdeckt sind

Klassiker: Jemand hat Daten nach /mnt/backup geschrieben, bevor die eigentliche Platte dort eingehängt war. Die Daten liegen weiter auf dem Wurzeldateisystem, sind aber vom darüberliegenden Mount verdeckt. Sichtbar wird das nur über eine zweite Ansicht desselben Dateisystems:

mkdir -p /mnt/rootview
mount --bind / /mnt/rootview
du -xh --max-depth=2 /mnt/rootview | sort -h
umount /mnt/rootview

Der Bind-Mount ist ungefährlich, er hängt nichts um und wird mit umount wieder entfernt.

Die root-Reserve und der Rechtefehler

Die bereits erwähnten fünf Prozent ext4-Reserve erklären die Lücke zwischen "noch nicht ganz voll" und "Dienste scheitern schon jetzt". Und schließlich: Wer du ohne root startet, sieht ganze Verzeichnisbäume nicht. Die vermeintliche Differenz ist dann schlicht ein Rechteproblem. Auf btrfs und ZFS kommen zusätzlich Snapshots als Erklärung in Frage, die du ebenfalls nie anzeigt.

Wenn nicht der Platz ausgeht, sondern die Inodes

Es gibt eine zweite Art von "voll", die exakt dieselbe Fehlermeldung erzeugt. Jede Datei und jedes Verzeichnis belegt einen Inode, und deren Anzahl steht bei ext4 seit dem Zeitpunkt der Formatierung fest.

df -i
stat -f /

Steht in df -i die Spalte IUse% auf 100, während df -h reichlich freien Platz meldet, ist die Diagnose eindeutig: Der Server hat kein Platzproblem, sondern ein Mengenproblem. Millionen winziger Dateien haben alle Inodes aufgebraucht. Die Fehlermeldung lautet trotzdem No space left on device, und genau deshalb suchen die meisten an der falschen Stelle.

Die Verursacher findet man, indem man Dateien zählt statt Bytes:

find /var -xdev -printf '%h\n' | sort | uniq -c | sort -rn | head -20

Häufige Kandidaten sind die Mailqueue unter /var/spool/postfix oder /var/spool/exim4, PHP-Sessions unter /var/lib/php/sessions, Cache-Verzeichnisse von Webanwendungen, ausgelagerte Node-Abhängigkeiten und ein nie geleertes /tmp.

Beim Löschen selbst wartet die nächste Hürde. rm /var/lib/php/sessions/* scheitert bei sehr vielen Dateien mit bash: /usr/bin/rm: Argument list too long, weil die Kommandozeile die Größengrenze überschreitet. Der Weg, der immer funktioniert:

find /var/lib/php/sessions -type f -mtime +7 -delete

Was man wissen muss: Die Inode-Anzahl eines bestehenden ext4-Dateisystems lässt sich nachträglich nicht erhöhen. Es hilft nur, das Dateisystem zu vergrößern (dabei wachsen die Inodes proportional mit) oder es mit dichterer Belegung neu anzulegen, etwa mit mkfs.ext4 -i 8192 /dev/sdb1. Wer absehbar mit sehr vielen kleinen Dateien arbeitet, etwa bei Mailservern oder Bildarchiven, ist mit XFS besser bedient, weil XFS Inodes dynamisch anlegt und praktisch erst dann ausgeht, wenn auch der Platz ausgeht. Debian und Ubuntu formatieren standardmäßig mit ext4, XFS muss man bewusst wählen.

Kontrollieren und dafür sorgen, dass es nicht wiederkommt

Eine Aufräumaktion gilt erst dann als erfolgreich, wenn drei Dinge zusammenpassen: df -h zeigt mehr freien Platz, df -i zeigt eine sinkende Inode-Belegung, und der Dienst, der ursprünglich ausgefallen ist, läuft wieder. Ein Befehl, der ohne Fehlermeldung durchgelaufen ist, ist für sich genommen noch kein Beweis. Besonders journalctl --vacuum-size und docker system prune tun gerne kommentarlos genau nichts.

Für den Dauerbetrieb bewährt sich eine kurze Liste: Journal per SystemMaxUse hart begrenzen, Docker-Logrotation im daemon.json setzen, auf Ubuntu Remove-Unused-Kernel-Packages aktivieren, eigene Anwendungslogs über /etc/logrotate.d/ erfassen und einen einfachen Cron-Job einrichten, der beim Überschreiten einer Schwelle eine Mail schickt. Auf KVM- und Dedicated-Servern lohnt es sich außerdem, /var oder zumindest /var/log auf eine eigene Partition zu legen. Dann legt ein Amoklauf im Log zwar einen Dienst lahm, aber nicht das Betriebssystem, und man kommt in jedem Fall noch per SSH auf die Maschine.

Häufige Fragen

Warum zeigt df 100 Prozent, obwohl du deutlich weniger findet?
In den allermeisten Fällen halten Prozesse gelöschte Dateien noch offen. Der Verzeichniseintrag ist weg, deshalb sieht du nichts mehr, die Blöcke bleiben aber belegt, bis der letzte Dateideskriptor geschlossen ist. Aufspüren mit "lsof +L1" als root, freigeben durch Neustart des Dienstes oder durch "truncate -s 0 /proc/PID/fd/N". Weitere Erklärungen sind unter Mountpoints verdeckte Dateien, die fünf Prozent root-Reserve bei ext4 und ein du-Lauf ohne root-Rechte.
Wie viel Platz darf das systemd-Journal belegen und wie begrenze ich es dauerhaft?
Standardmäßig zehn Prozent des Dateisystems, gedeckelt bei vier Gigabyte, zusätzlich hält journald 15 Prozent des Dateisystems frei. Dauerhaft begrenzt man es über eine Datei unter /etc/systemd/journald.conf.d/ mit SystemMaxUse, danach systemd-journald neu starten. Für sofortige Wirkung erst "journalctl --rotate" und dann "journalctl --vacuum-size=200M", weil vacuum nur archivierte Journaldateien entfernt.
Liegt das Journal auf Debian und Ubuntu gleich?
Nein. Bei der Voreinstellung Storage=auto entscheidet allein, ob /var/log/journal existiert. Auf vielen minimalen Debian 12 und Debian 13 Installationen fehlt dieses Verzeichnis, das Journal liegt dann im RAM unter /run/log/journal und verschwindet beim Neustart. Ubuntu Server 22.04 und 24.04 legen das Verzeichnis üblicherweise an und protokollieren auf die Platte. Ein "ls -d /var/log/journal" klärt das in einer Sekunde.
/boot ist voll und apt bricht mit einem dpkg-Fehler ab. Was jetzt?
Zuerst "uname -r" notieren, dann in /boot die älteste, nicht laufende Version heraussuchen und nur deren initrd.img löschen, nicht die vmlinuz-Datei. Danach "apt-get -f install", damit dpkg die abgebrochene Konfiguration abschließt, anschließend "apt-get autoremove --purge" und zum Schluss "update-grub". Wahllos Kernel-Dateien löschen führt zu GRUB-Einträgen, die ins Leere zeigen.
Ist "docker system prune -a --volumes" sicher?
Auf einem Produktivserver ohne aktuelles Backup nicht. Der Schalter --volumes entfernt auch Volumes, an denen gerade kein laufender Container hängt, also unter Umständen die Datenbank eines gestoppten Containers. Sicherer ist der Weg über "docker system df -v" zur Diagnose und danach gezielt "docker image prune -a" beziehungsweise "docker builder prune".
Was tun, wenn df -i die Inodes zu 100 Prozent belegt zeigt?
Dann fehlt nicht Platz, sondern die Anzahl möglicher Dateien. Die Verursacher findet man mit "find /var -xdev -printf '%h\n' | sort | uniq -c | sort -rn | head -20", typischerweise Mailqueue, PHP-Sessions, Caches oder /tmp. Gelöscht wird mit find und -delete, weil rm mit Sternchen an "Argument list too long" scheitert. Die Inode-Anzahl eines ext4-Dateisystems lässt sich nachträglich nicht erhöhen, nur durch Vergrößern oder Neuanlegen des Dateisystems, alternativ XFS verwenden.

Linux Serververwaltung Debian Ubuntu Speicherplatz systemd Docker Troubleshooting