Schijf vol: opslagruimte op de Linux-server vinden en vrijmaken

Gepubliceerd op 13 min leestijd

Wanneer df 100% meldt en du niets vindt: de volledige route van meten tot vrijmaken, voor Debian 12 en 13 en voor Ubuntu 22.04 en 24.04.

Een volle schijf meldt zich zelden beleefd. Meestal valt eerst een dienst uit: MariaDB schrijft OS error 28 in het log, nginx antwoordt met een 500, een back-up breekt af met write error: No space left on device, en als het echt tegenzit komt niemand nog via SSH op de machine, omdat sshd geen sessiebestanden meer kan aanmaken. Dit artikel laat de volledige route zien: meten, vinden, vrijmaken, controleren. Inclusief de twee gevallen waarbij de meeste handleidingen ophouden, namelijk wanneer df vol meldt en du niets vindt.

Eerst meten: welke partitie is er eigenlijk vol?

Voordat er ook maar iets wordt verwijderd, moet duidelijk zijn welk bestandssysteem het betreft. Een volle /boot heeft heel andere oorzaken dan een volle /var.

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

De tweede regel verbergt de pseudobestandssystemen. Op Ubuntu is dat bijzonder nuttig, want daar duikt elke geïnstalleerde Snap-toepassing op als eigen squashfs-loop-mount en vervuilt zo de uitvoer. Die loop-mounts zijn overigens altijd voor 100% bezet, dat is normaal en geen probleem.

Belangrijk is de kolom Mounted on. Typische situaties op servers:

MountpointGebruikelijke oorzaak als het vol is
/logs, Docker, apt-cache, applicatiegegevens
/bootoude kernels en hun initramfs-images
/varjournal, rsyslog, mailwachtrij, databases, Docker
/tmpafgebroken uploads, sessies, buildresten

Nog een detail dat vaak voor verwarring zorgt: ext4 reserveert standaard vijf procent van de capaciteit voor de gebruiker root. Een dienst die als www-data of mysql draait, krijgt dus al een No space left on device, terwijl root op datzelfde moment nog probleemloos kan schrijven. Op partities die alleen data bevatten (dus niet op het rootbestandssysteem) mag u die reserve zonder risico verlagen:

tune2fs -m 1 /dev/sdb1

Op / moet de reserve blijven staan. Dat is precies de buffer waarmee u een volgelopen systeem nog kunt repareren.

du goed inzetten in plaats van verdwalen

De klassieke aanpak werkt één niveau tegelijk, altijd met -x:

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

De -x is de belangrijkste optie in dit hele artikel. Die houdt du binnen één bestandssysteem en voorkomt dat het commando afdaalt in /proc, /sys, aangekoppelde netwerkshares of back-upschijven. Zonder -x loopt de zoekactie minutenlang en levert getallen op die niets met de volle partitie te maken hebben. sort -h sorteert de leesbare groottes correct, zodat de grootste brok onderaan staat.

Klikt u liever interactief door de mappen, installeer dan ncdu en start dat eveneens met -x:

apt-get install -y ncdu
ncdu -x /

Losse grote bestanden vindt u sneller rechtstreeks:

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

Twee valkuilen leiden hier regelmatig tot verkeerde conclusies. Ten eerste: du telt bezette blokken, niet de logische bestandsgrootte. Bij sparse bestanden (database-tablespaces, images van virtuele schijven) lopen beide waarden ver uiteen. De vergelijking maakt dat zichtbaar:

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

Ten tweede: du telt harde links maar één keer. Werkt u zonder root, dan krijgt u bovendien regels als du: cannot read directory '/var/lib/private': Permission denied en daarmee structureel te lage totalen. Voer alle analyses dus als root uit, of met sudo.

Het systemd-journal begrenzen

Op servers is het journal de meest voorkomende stille slokop van schijfruimte. Standaard mag het tien procent van het bestandssysteem gebruiken, met een plafond van vier gigabyte, en daarnaast houdt het 15% van het bestandssysteem vrij. Op een schijf van 500 gigabyte is dat tot vier gigabyte aan puur log.

journalctl --disk-usage

Hier zit een echt verschil tussen de distributies, dat veel handleidingen weglaten. Of het journal daadwerkelijk op de schijf terechtkomt, hangt bij de standaardinstelling Storage=auto uitsluitend af van de vraag of de map /var/log/journal bestaat:

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

Op minimale Debian-installaties en op veel cloud-images van Debian 12 en Debian 13 bestaat /var/log/journal niet. Het journal staat dan onder /run/log/journal, dus in RAM, verdwijnt bij elke herstart en belast de schijf helemaal niet. Het belast in plaats daarvan het werkgeheugen. Ubuntu Server 22.04 en 24.04 maken de map juist meestal wel aan en loggen permanent naar de schijf. Controleren in plaats van gokken.

Direct ruimte vrijmaken:

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

De aanroep van --rotate daarvoor is geen versiering: de vacuum-opties verwijderen uitsluitend journalbestanden die al gearchiveerd zijn, nooit het bestand dat op dat moment actief is. Als juist dat actieve bestand het grootste deel uitmaakt, lijkt er zonder voorafgaand roteren niets te gebeuren. Precies daarover struikelen lezers die het commando uit een forumbericht overnemen.

Voor een blijvende begrenzing gebruikt u een eigen bestand, zodat latere pakketupdates niets overschrijven:

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

Controle of het echt heeft gewerkt: journalctl --disk-usage moet nu een kleinere waarde melden en df -h moet meer vrije ruimte tonen. Krimpt het journal terwijl df onveranderd blijft, dan houdt een proces nog verwijderde bestanden open. Daarover verderop meer.

Tweede verschil tussen de distributies: op klassieke serverinstallaties van Debian en Ubuntu draait vaak ook rsyslog mee, dat dezelfde meldingen een tweede keer naar /var/log/syslog schrijft. Op minimale Ubuntu-images (cloud, container) ontbreekt rsyslog juist. Controleer dat met ls -l /var/log/syslog. Bestaat het bestand en is het enorm groot, dan is niet het journal het probleem, maar een ontbrekende of kapotte logrotate-regel onder /etc/logrotate.d/.

apt-cache en oude kernels

Gedownloade pakketten blijven na de installatie staan. Op een server die lang draait, is dat al snel meerdere gigabytes.

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

apt-get clean leegt /var/cache/apt/archives volledig, apt-get autoclean alleen de pakketten die niet meer in de pakketbronnen staan. Wat clean niet aanraakt, zijn de pakketlijsten onder /var/lib/apt/lists. Bij veel geconfigureerde pakketbronnen kunnen die enkele honderden megabytes bereiken en ze zijn zonder risico opnieuw op te bouwen:

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

De tweede regel is daarbij geen vrije keuze maar een verplichting, en wel meteen. Tussen het verwijderen van de lijsten en de volgende apt-get update kent apt geen enkel pakket meer: elke apt-get install breekt in die toestand af met E: Unable to locate package ... en exitcode 100, ook al staat het pakket vanzelfsprekend gewoon in de pakketbronnen.

De tweede klassieker zijn oude kernels. Ubuntu installeert via unattended-upgrades doorlopend nieuwe kernels, maar ruimt de oude alleen op als u dat uitdrukkelijk toestaat. Elke kernel neemt samen met zijn initramfs ongeveer 100 tot 150 megabyte in beslag in een /boot dat vaak maar 512 megabyte tot één gigabyte groot is.

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

De draaiende kernel uit uname -r wordt daarbij nooit verwijderd, en de nieuwste evenmin. Op Ubuntu voorkomt u het probleem door in /etc/apt/apt.conf.d/50unattended-upgrades de regel Unattended-Upgrade::Remove-Unused-Kernel-Packages "true"; te zetten. Op Debian 12 en 13 is unattended-upgrades standaard niet actief; daar groeit /boot alleen als iemand regelmatig met de hand bijwerkt en nooit opruimt.

Wanneer /boot al vol is en apt niet meer doorloopt

Dit is het geval dat echt pijn doet. Typische meldingen, letterlijk:

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)

Nu staat de pakketdatabase in een halfafgewerkte toestand en elke volgende apt-aanroep loopt op hetzelfde punt vast. De uitweg, in deze volgorde:

  1. uname -r noteren. Aan die versie wordt onder geen beding iets veranderd.
  2. ls -lh /boot uitvoeren en de oudste versie opzoeken die niet draait.
  3. Alleen de bijbehorende initrd.img-* verwijderen, niet de vmlinuz-*. Het initramfs-bestand is verreweg het grootste en kan op elk moment opnieuw worden aangemaakt.
  4. apt-get -f install uitvoeren, zodat dpkg de afgebroken configuratie kan afronden.
  5. Pas daarna apt-get autoremove --purge, zodat de oude pakketten netjes en inclusief hun GRUB-vermelding verdwijnen.
  6. update-grub laten lopen en de uitvoer lezen.

Wat u niet moet doen: met rm lukraak kernelbestanden uit /boot verwijderen en de rest negeren. dpkg denkt dan nog steeds dat de pakketten geïnstalleerd zijn, GRUB biedt vermeldingen aan die nergens heen wijzen, en de volgende herstart eindigt in de reddingsprompt van GRUB. Hebt u al iets verwijderd, herstel de consistentie dan met apt-get install --reinstall van het betreffende pakket of met dpkg --purge, gevolgd door een verplichte update-grub.

Controle: df -h /boot toont weer vrije ruimte, dpkg -l 'linux-image-*' laat nog maar twee of drie regels met status ii zien, en de uitvoer van update-grub noemt precies de kernels die ook echt in /boot staan.

Docker, Snap en containerlogs

Op Docker-hosts ligt het antwoord bijna altijd in /var/lib/docker. Niet gokken, maar navragen:

docker system df
docker system df -v

De uitgebreide variant splitst netjes uit naar images, containers, volumes en buildcache. Daarna gericht opruimen:

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

Een woord van waarschuwing bij --volumes: die optie verwijdert ook volumes waaraan geen draaiende container hangt. Draait uw database in een benoemd volume en hebt u de container net gestopt, dan bent u de data kwijt. Zonder actuele back-up hoort docker system prune -a --volumes niet thuis op een productieserver.

De onderschatte post zijn de containerlogs onder /var/lib/docker/containers/*/*-json.log. De standaarddriver json-file roteert zonder uitdrukkelijke instelling helemaal niet. Een spraakzame container schrijft zo in de loop van maanden tientallen gigabytes naar één enkel bestand. Het tegengif komt in /etc/docker/daemon.json:

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

Daarna systemctl restart docker. Let op, en dat staat in vrijwel geen enkele handleiding: de instelling geldt alleen voor nieuw aangemaakte containers. Bestaande containers houden hun oude configuratie tot ze één keer opnieuw worden aangemaakt, bij Compose dus via docker compose up -d --force-recreate.

De Docker-versies verschillen hier duidelijk. Debian 12 levert docker.io 20.10, Debian 13 komt met 26.1, Ubuntu 22.04 en 24.04 zitten inmiddels op 29.1. Hoe nieuwer de versie, hoe meer ruimte de BuildKit-cache inneemt en hoe belangrijker docker builder prune wordt, want docker system prune alleen leegt die niet altijd volledig.

Op Ubuntu komt Snap daar nog bij. Oude revisies blijven uitgeschakeld staan en nemen gewoon ruimte in beslag:

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

De waarde 2 is het minimum dat snapd accepteert, kleinere waarden worden met een foutmelding geweigerd. Debian heeft geen Snap aan boord, daar vervalt dit onderdeel volledig.

df meldt vol, du vindt niets

Nu het interessante geval. df -h toont 100%, maar het totaal uit du komt niet verder dan de helft. Daarvoor zijn er precies vier plausibele oorzaken.

Verwijderde bestanden die nog geopend zijn

Dit is verreweg de meest voorkomende oorzaak. Iemand heeft rm /var/log/riesig.log uitgevoerd terwijl een dienst het bestand nog open had staan. De vermelding in de map is verdwenen, daarom ziet du niets meer. De blokken blijven bezet tot de laatste bestandsdescriptor is gesloten, daarom ziet df ze nog wel.

apt-get install -y lsof
lsof +L1

In de kolom NLINK staat dan een 0 en achter het pad staat (deleted). Vindt lsof niets, dan is er geen uitvoer en is de retourwaarde 1; dat is geen fout. Zonder lsof kan het ook rechtstreeks via het procesbestandssysteem:

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

Belangrijk: voer dit beslist als root uit. Als gewone gebruiker ziet u alleen uw eigen descriptors en mist u juist die systeemdiensten die in negen van de tien gevallen de veroorzaker zijn.

De nette manier om de ruimte vrij te geven is een herstart van de dienst, bijvoorbeeld systemctl restart rsyslog. Komt een herstart niet in aanmerking, dan kunt u het bestand via zijn descriptor tot lengte nul inkorten. Proces-ID en descriptornummer komen uit de uitvoer van lsof:

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

Dat geeft de blokken meteen vrij en het proces schrijft daarna gewoon door. Deze methode is bedoeld voor zuivere logbestanden die in append-modus geopend zijn. Op databasebestanden, images van virtuele machines of alles wat verder willekeurige toegang gebruikt, mag u deze methode nooit toepassen, daar leidt ze tot dataverlies.

Waaraan u ziet dat het gelukt is: df -h toont meteen meer vrije ruimte en lsof +L1 toont de regel niet meer. Een tweede ronde van du verandert daarentegen helemaal niet, want daar was het bestand toch al onzichtbaar. Precies daarom lijkt een herstart van de server het probleem op magische wijze op te lossen.

Bestanden die onder een mountpoint verborgen zitten

Een klassieker: iemand heeft data naar /mnt/backup geschreven voordat de eigenlijke schijf daar was aangekoppeld. Die data staat nog steeds op het rootbestandssysteem, maar wordt verborgen door de mount die eroverheen ligt. Zichtbaar wordt dat alleen via een tweede weergave van hetzelfde bestandssysteem:

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

De bind-mount is ongevaarlijk, hij verplaatst niets en wordt met umount weer verwijderd.

De root-reserve en de rechtenfout

De al genoemde vijf procent ext4-reserve verklaart het gat tussen "nog niet helemaal vol" en "diensten vallen nu al uit". En tot slot: wie du zonder root start, ziet hele directorystructuren niet. Het vermeende verschil is dan simpelweg een rechtenprobleem. Op btrfs en ZFS komen daar snapshots als verklaring bij, die du eveneens nooit toont.

Wanneer niet de ruimte opraakt, maar de inodes

Er bestaat een tweede soort "vol" die exact dezelfde foutmelding oplevert. Elk bestand en elke map bezet een inode, en bij ext4 ligt dat aantal vast vanaf het moment van formatteren.

df -i
stat -f /

Staat in df -i de kolom IUse% op 100 terwijl df -h ruim voldoende vrije ruimte meldt, dan is de diagnose duidelijk: de server heeft geen gebrek aan ruimte, het aantal mogelijke bestanden is op. Miljoenen piepkleine bestanden hebben alle inodes verbruikt. De foutmelding luidt desondanks No space left on device, en juist daarom zoeken de meeste mensen op de verkeerde plek.

De veroorzakers vindt u door bestanden te tellen in plaats van bytes:

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

Veelvoorkomende kandidaten zijn de mailwachtrij onder /var/spool/postfix of /var/spool/exim4, PHP-sessies onder /var/lib/php/sessions, cachemappen van webapplicaties, uitgedijde Node-afhankelijkheden en een /tmp die nooit wordt geleegd.

Bij het verwijderen zelf wacht de volgende horde. rm /var/lib/php/sessions/* mislukt bij heel veel bestanden met bash: /usr/bin/rm: Argument list too long, omdat de commandoregel de groottelimiet overschrijdt. De aanpak die altijd werkt:

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

Wat u moet weten: het aantal inodes van een bestaand ext4-bestandssysteem valt achteraf niet te verhogen. Het helpt alleen om het bestandssysteem te vergroten (daarbij groeit het aantal inodes evenredig mee) of om het opnieuw aan te maken met een dichtere bezetting, bijvoorbeeld met mkfs.ext4 -i 8192 /dev/sdb1. Wie naar verwachting met heel veel kleine bestanden werkt, bijvoorbeeld bij mailservers of beeldarchieven, is beter af met XFS, omdat XFS inodes dynamisch aanmaakt en praktisch pas opraakt wanneer ook de ruimte opraakt. Debian en Ubuntu formatteren standaard met ext4, XFS moet u bewust kiezen.

Controleren en zorgen dat het niet terugkomt

Een opruimactie geldt pas als geslaagd wanneer drie dingen kloppen: df -h toont meer vrije ruimte, df -i laat een dalende inodebezetting zien, en de dienst die oorspronkelijk uitviel, draait weer. Een commando dat zonder foutmelding is doorgelopen, is op zichzelf nog geen bewijs. Vooral journalctl --vacuum-size en docker system prune doen graag zonder enige melding precies niets.

Voor de dagelijkse praktijk bewijst een korte lijst zijn nut: het journal hard begrenzen met SystemMaxUse, de Docker-logrotatie in de daemon.json instellen, op Ubuntu Remove-Unused-Kernel-Packages inschakelen, eigen applicatielogs via /etc/logrotate.d/ meenemen en een eenvoudige cronjob inrichten die een mail stuurt zodra een drempel wordt overschreden. Op KVM-servers en dedicated servers loont het bovendien om /var, of op zijn minst /var/log, op een eigen partitie te zetten. Een log dat op hol slaat legt dan wel een dienst plat, maar niet het besturingssysteem, en u komt in elk geval nog via SSH op de machine.

Veelgestelde vragen

Waarom toont df 100% terwijl du duidelijk minder vindt?
In verreweg de meeste gevallen houden processen verwijderde bestanden nog open. De vermelding in de map is verdwenen, daarom ziet du niets meer, maar de blokken blijven bezet tot de laatste bestandsdescriptor is gesloten. Opsporen doet u met "lsof +L1" als root, vrijgeven met een herstart van de dienst of met "truncate -s 0 /proc/PID/fd/N". Andere verklaringen zijn bestanden die onder een mountpoint verborgen zitten, de vijf procent root-reserve bij ext4 en een du-ronde zonder rootrechten.
Hoeveel ruimte mag het systemd-journal innemen en hoe begrens ik dat blijvend?
Standaard tien procent van het bestandssysteem, met een plafond van vier gigabyte; daarnaast houdt journald 15% van het bestandssysteem vrij. Blijvend begrenst u het via een bestand onder /etc/systemd/journald.conf.d/ met SystemMaxUse, waarna u systemd-journald herstart. Voor onmiddellijk effect eerst "journalctl --rotate" en pas daarna "journalctl --vacuum-size=200M", want vacuum verwijdert alleen journalbestanden die al gearchiveerd zijn.
Staat het journal op Debian en Ubuntu op dezelfde plek?
Nee. Bij de standaardinstelling Storage=auto is alleen bepalend of /var/log/journal bestaat. Op veel minimale installaties van Debian 12 en Debian 13 ontbreekt die map, waardoor het journal in RAM staat onder /run/log/journal en bij elke herstart verdwijnt. Ubuntu Server 22.04 en 24.04 maken de map meestal wel aan en loggen naar de schijf. Een "ls -d /var/log/journal" maakt dat in een seconde duidelijk.
/boot is vol en apt breekt af met een dpkg-fout. Wat nu?
Noteer eerst "uname -r", zoek daarna in /boot de oudste versie op die niet draait en verwijder alleen de bijbehorende initrd.img, niet het vmlinuz-bestand. Voer vervolgens "apt-get -f install" uit, zodat dpkg de afgebroken configuratie afrondt, daarna "apt-get autoremove --purge" en tot slot "update-grub". Lukraak kernelbestanden verwijderen leidt tot GRUB-vermeldingen die nergens heen wijzen.
Is "docker system prune -a --volumes" veilig?
Op een productieserver zonder actuele back-up niet. De optie --volumes verwijdert ook volumes waaraan op dat moment geen draaiende container hangt, dus mogelijk de database van een gestopte container. Veiliger is de route via "docker system df -v" voor de diagnose en daarna gericht "docker image prune -a" of "docker builder prune".
Wat te doen als df -i de inodes voor 100% bezet toont?
Dan ontbreekt het niet aan ruimte, maar aan het aantal mogelijke bestanden. De veroorzakers vindt u met "find /var -xdev -printf '%h\n' | sort | uniq -c | sort -rn | head -20", meestal de mailwachtrij, PHP-sessies, caches of /tmp. Verwijderen gaat met find en -delete, omdat rm met een sterretje afbreekt op "Argument list too long". Het aantal inodes van een ext4-bestandssysteem valt achteraf niet te verhogen, alleen door het bestandssysteem te vergroten of opnieuw aan te maken, of anders door XFS te gebruiken.

Linux Serverbeheer Debian Ubuntu Schijfruimte systemd Docker Probleemoplossing