SSD Nodes Learn 🎉 VPS dès $5.50/mois
Guides Matt ConnorPar Matt Connor

Pourquoi df est plein alors que du ne trouve rien

Votre VPS affiche « df : disque plein », mais du ne voit pas l’espace. Trouvez le fichier supprimé encore ouvert par un processus avec lsof, sans redémarrer.

Pourquoi df indique que le disque est plein alors que du donne un autre résultat

df indique que le disque est plein, alors que du ne trouve pas l’espace utilisé parce qu’un processus maintient ouvert un fichier qui a été supprimé. La suppression d’un fichier retire son nom du répertoire. Les blocs de données ne sont libérés que lorsque le dernier descripteur de fichier ouvert qui pointe vers cet inode est fermé. du parcourt les noms, donc il ne compte rien. df demande au système de fichiers combien de blocs sont alloués, donc il compte encore le fichier qui n’a plus de nom.

Ce guide reproduit le problème sur un VPS Ubuntu standard avec les outils déjà installés, identifie le processus qui maintient le fichier ouvert grâce à /proc, puis libère l’espace sans redémarrage. Les autres causes du même symptôme sont ensuite présentées : une table d’inodes sans entrée disponible, des fichiers masqués sous un point de montage et des blocs réservés à root.

Exécutez chaque commande et examinez votre propre sortie. Les valeurs dépendent de votre disque. Comparez donc les résultats avant et après sur votre propre machine, plutôt qu’avec une valeur indiquée dans un guide.

Ce que comptent df et du

df (disk free) interroge chaque système de fichiers monté pour obtenir ses propres statistiques : le nombre de blocs existants, le nombre de blocs alloués et le nombre de blocs libres. Il n’ouvre jamais de répertoire. Le résultat couvre tous les blocs alloués, y compris ceux appartenant à un fichier vers lequel aucune entrée de répertoire ne pointe.

du (disk usage) fait l’inverse. Il part du chemin que vous lui indiquez, lit les répertoires, examine chaque entrée trouvée et additionne les blocs. Un fichier sans nom lui est invisible. Il en va de même pour tout répertoire qu’il n’a pas l’autorisation de lire, ce qui explique qu’un utilisateur ordinaire obtienne un total inférieur à celui de root. Exécutez du avec sudo avant de tirer une conclusion de la comparaison.

Deux options sont importantes chaque fois que vous comparez les deux commandes.

  • -x limite du à un seul système de fichiers. Sans cette option, du / parcourt tous les systèmes de fichiers montés sous / et produit un total que df / ne mesurait pas.
  • -s affiche une ligne récapitulative par argument au lieu d’une ligne par répertoire.

Vous pouvez donc exécuter ces deux commandes côte à côte sur le système de fichiers qui vous intéresse.

df -h /
sudo du -xhs / 2>/dev/null

df répond immédiatement. du peut prendre plusieurs minutes sur un système de fichiers volumineux, car il examine chaque fichier rencontré. Lorsque les deux totaux sont très différents et que du a été exécuté en tant que root avec -x, l’espace manquant est alloué à quelque chose qui n’a pas de nom.

Reproduire volontairement l’écart

Faites-le sur un VPS de test. Tout ce qui suit utilise bash et coreutils. Rien ne doit être installé.

Enregistrez l’état initial du système de fichiers qui contient /var/tmp.

cd /var/tmp
df -h .
df --output=used -B1 .

La deuxième commande affiche les octets utilisés sans arrondi. La vérification finale sera donc exacte.

Créez maintenant un fichier. Sa taille est calculée à partir de l’espace libre indiqué par la machine. La démonstration s’adapte donc à la taille de votre disque.

free=$(df --output=avail -B1 . | tail -n 1)
fallocate -l $((free / 10)) ghost.bin
ls -l ghost.bin
df -h .

$(...) correspond à la substitution de commande : le shell exécute la commande placée entre les délimiteurs, puis utilise sa sortie comme valeur de free. Si cette syntaxe vous est inconnue, la substitution de commande dans bash l’explique en détail. fallocate réserve les blocs réels sans y écrire de données, ce qui explique son exécution instantanée. Si le système de fichiers ne prend pas cette fonctionnalité en charge, la commande échoue. head -c $((free / 10)) /dev/zero > ghost.bin produit alors le même résultat en écrivant les octets.

Comparez cet df -h . avec celui que vous avez enregistré. La colonne des blocs utilisés a augmenté et celle de l’espace disponible a diminué.

Ouvrez maintenant le fichier depuis un autre processus, puis supprimez-le.

sleep infinity < ghost.bin &
holder=$!
rm ghost.bin
ls -l ghost.bin
df -h .
sudo du -xhs . 2>/dev/null

La redirection est l’élément essentiel. sleep infinity < ghost.bin & démarre un processus en arrière-plan dont l’entrée standard correspond à ce fichier. Le shell ouvre donc le fichier et transmet le descripteur à sleep, qui le maintient ouvert. $! contient l’identifiant du processus de cette tâche en arrière-plan. rm supprime ensuite le nom alors que le descripteur reste ouvert.

Lisez la sortie. ls ne trouve pas le fichier, car son nom a disparu. du revient près de sa valeur initiale, car il parcourt les noms. df n’a pas changé, car les blocs sont toujours alloués. Le système de fichiers et l’arborescence des répertoires sont maintenant en désaccord. L’écart entre les deux correspond au fichier que vous venez de supprimer.

Rechercher le processus qui conserve le fichier supprimé

Chaque descripteur de fichier ouvert apparaît sous /proc/<pid>/fd/ sous la forme d’un lien symbolique vers le fichier qu’il désigne. Lorsque le fichier a été désalloué, le noyau marque la cible de ce lien comme supprimée. Il faut donc rechercher un lien dont la cible porte cette marque.

sudo find /proc/[0-9]*/fd -lname '*(deleted)' -printf '%p -> %l\n' 2>/dev/null

-lname recherche la cible d’un lien symbolique plutôt que son nom, %p affiche le chemin du descripteur et %l affiche ce vers quoi il pointe. Le PID est le deuxième élément du chemin affiché. Exécutez la commande avec sudo, car sinon vous ne pouvez lire /proc/<pid>/fd que pour vos propres processus. La redirection de stderr supprime les messages des processus qui se terminent pendant que find parcourt l’arborescence.

Un serveur actif conserve plusieurs fichiers supprimés à tout moment. La plupart sont petits et sans conséquence. Triez-les par taille pour placer les fichiers intéressants en tête.

sudo bash -c 'for fd in /proc/[0-9]*/fd/*; do
  target=$(readlink "$fd" 2>/dev/null) || continue
  case "$target" in
    *"(deleted)") echo "$(stat -Lc %s "$fd" 2>/dev/null) $fd $target" ;;
  esac
done' | sort -rn | head

stat -L suit le lien jusqu’à l’inode lui-même. Ainsi, %s indique la taille du fichier qui n’a plus de nom. Le tri sur ce nombre place le plus volumineux en premier.

Identifiez ensuite le processus associé au descripteur retenu. Le chemin placé en tête de la liste contient les deux nombres nécessaires. Stockez-les d’abord dans des variables en remplaçant PID et N par les valeurs affichées dans votre propre liste.

pid=PID
n=N
ps -o pid,user,etime,args -p "$pid"
sudo stat -L "/proc/$pid/fd/$n"

ps indique le programme et sa durée d’exécution. stat -L affiche la taille et le nombre de blocs alloués de l’inode supprimé. Ces informations répondent ensemble à la question essentielle : quel service conserve ce fichier ouvert.

Si la machine dispose déjà de lsof, sudo lsof +L1 répertorie les fichiers ouverts dont le nombre de liens est tombé à zéro et affiche leurs tailles dans un même tableau. Cet outil n’est pas présent sur une image Ubuntu minimale. De plus, l’installation d’un paquet sur un système de fichiers sans espace libre peut elle-même échouer. Le parcours avec /proc est donc la méthode qui fonctionne toujours.

Libérer l’espace sans redémarrer

Un redémarrage règle effectivement le problème, mais ce n’est pas la première mesure à prendre : il interrompt le service et détruit les éléments utiles au diagnostic. Quatre options moins brutales sont possibles, dans l’ordre où les essayer.

Commencez par copier les données ailleurs si vous souhaitez les conserver. La lecture du chemin du descripteur lit l’inode actif.

sudo cp /proc/<pid>/fd/<n> /root/recovered.log

C’est le seul cas où un fichier supprimé est facile à récupérer. C’est pourquoi récupérer des fichiers supprimés avec rm -rf commence par vérifier si un processus maintient encore le fichier ouvert. Une fois le dernier descripteur fermé, cette possibilité disparaît.

Ensuite, videz le fichier via le descripteur. Le chemin /proc mène au même inode. La troncature libère donc les blocs, tandis que le processus continue de fonctionner.

sudo truncate -s 0 "/proc/$pid/fd/$n"
df -h /

Cette méthode fonctionne correctement lorsque le processus d’écriture a ouvert le fichier en mode append, car chaque écriture va alors à la fin actuelle du fichier. Sinon, le processus conserve son ancien offset d’écriture. Sa prochaine écriture se fait donc très loin dans le fichier et le recrée avec un trou au début. Un trou n’est pas alloué : les blocs restent donc libres et df conserve l’espace qu’il vient de libérer. Seule la taille réapparaît : exécutez de nouveau sudo stat -L "/proc/$pid/fd/$n" après l’écriture du processus. La commande affiche alors l’ancienne taille avec un nombre de blocs qui ne correspond plus. Redémarrez le processus si vous voulez également que la taille reparte de zéro.

Troisièmement, demandez au service de rouvrir ses journaux. Un daemon dont le fichier de journalisation a été supprimé alors qu’il était ouvert est le cas réel le plus fréquent. De nombreux daemons rouvrent leurs fichiers de journalisation à la réception d’un signal : nginx utilise SIGUSR1 et rsyslog utilise SIGHUP. Consultez la documentation du daemon concerné au lieu de deviner, car l’envoi du mauvais signal au mauvais daemon peut l’arrêter.

sudo systemctl kill -s USR1 nginx

Quatrièmement, redémarrez l’unité. sudo systemctl restart <unit> ferme tous les descripteurs détenus par l’ancien processus. Les blocs reviennent donc avec certitude. Dans la démonstration précédente, le détenteur est un sleep que vous avez lancé vous-même. Il suffit donc de le terminer.

kill $holder
df -h .
df --output=used -B1 .
sudo find /proc/[0-9]*/fd -lname '*(deleted)' -printf '%p -> %l\n' 2>/dev/null

Comparez les octets utilisés avec la valeur relevée avant la création du fichier. Les valeurs correspondent de nouveau, et find ne signale plus votre descripteur. Vérifier le résultat avec la même commande que celle utilisée pour trouver le problème est une habitude utile à conserver.

Il est plus simple de surveiller cette valeur que d’exécuter df manuellement à répétition. watch répète une commande à intervalle fixe et réaffiche la sortie au même endroit. watch df -h / permet donc de voir la colonne utilisée changer à mesure que l’espace est libéré.

Lorsque les totaux concordent alors que le disque reste plein

Si df et un du -x root concordent, aucun fichier supprimé n’est en cause. Les causes restantes sont d’une autre nature et chacune nécessite une vérification spécifique.

Plus d’inodes, pas plus de blocs

Un inode contient les métadonnées d’un fichier. ext4 crée un nombre fixe d’inodes lors de la création du système de fichiers. Un système de fichiers peut donc manquer d’inodes alors qu’il dispose encore de blocs libres. La création de nouveaux fichiers échoue alors, même si df -h indique qu’il reste de l’espace.

df -h /
df -i /

La première commande compte les blocs et la seconde compte les inodes. Comparez la colonne d’utilisation de chacune. Une faible utilisation des blocs et une utilisation des inodes à la limite indiquent que le problème vient d’un très grand nombre de fichiers très petits.

df refuse -i et --output dans le même appel. Pour obtenir les valeurs brutes à lire ou à transmettre à une autre commande, sélectionnez les champs d’inodes par leur nom et n’utilisez pas -i.

df --output=itotal,iused,iavail,ipcent /

Ces colonnes contiennent les mêmes informations de comptabilisation que celles affichées par df -i, dans un format que vous pouvez analyser.

Trouvez les fichiers en comptant les entrées plutôt que les octets.

sudo du --inodes -x -d 1 / 2>/dev/null | sort -rn | head

Répétez la même commande un niveau plus bas dans le répertoire arrivé en tête, jusqu’à atteindre l’arborescence qui crée les fichiers. Si votre du ne prend pas en charge --inodes, sudo find /var -xdev -type f | wc -l compte un sous-arbre plus lentement.

La solution consiste à supprimer ou à déplacer ces fichiers. Vous ne pouvez pas ajouter d’inodes à un système de fichiers ext4 existant, car leur nombre est fixé lors de mkfs. Pour l’augmenter, il faut donc recréer le système de fichiers et restaurer une sauvegarde. XFS alloue les inodes selon les besoins et n’est donc pas soumis à la même limite fixe. Une machine qui exécute des conteneurs atteint plus rapidement les deux limites que la plupart des autres machines, car les couches d’image contiennent de nombreux fichiers de petite taille. Sur cette machine, réduire l’utilisation du disque par Docker sur un VPS est la solution adaptée. Cette opération libère bien plus d’espace qu’un nettoyage général du système de fichiers.

Espace masqué sous un point de montage

Un répertoire peut contenir des fichiers avant qu’un système de fichiers y soit monté. Montez un système de fichiers sur ce répertoire : les fichiers sous-jacents restent exactement à leur emplacement, toujours alloués, toujours comptabilisés par df, mais ne sont plus accessibles par leur nom. du ne peut pas les voir, car le montage les masque.

Montrez-le avec tmpfs, qui ne nécessite aucun espace disque supplémentaire. Cette partie nécessite une machine sur laquelle vous avez l’autorisation de monter des systèmes de fichiers. Elle fonctionne donc sur un VPS KVM.

sudo mkdir -p /srv/covered
sudo cp /etc/services /srv/covered/
ls /srv/covered
sudo mount -t tmpfs tmpfs /srv/covered
ls /srv/covered
sudo umount /srv/covered
ls /srv/covered

Le ls du milieu affiche un répertoire vide. La copie n’a jamais été déplacée : elle se trouve toujours sur le système de fichiers racine et réapparaît dès que vous démontez le système de fichiers. Imaginez maintenant un service qui a écrit dans ce chemin pendant un mois avant qu’une personne n’y monte un volume.

Pour trouver ces fichiers sur un serveur en production, montez une deuxième fois le système de fichiers racine à un autre emplacement. Un bind mount affiche un système de fichiers sans les systèmes de fichiers montés à l’intérieur.

sudo mkdir -p /mnt/rootcheck
sudo mount --bind / /mnt/rootcheck
sudo du -xhs /mnt/rootcheck/* 2>/dev/null | sort -h
sudo umount /mnt/rootcheck

Tout ce qui apparaît dans cette liste, mais pas sous le chemin normal, est masqué sous un point de montage. Démontez le bind mount lorsque vous avez terminé. Sinon, un du ultérieur sans -x comptabilisera deux fois les mêmes fichiers.

Blocs réservés à root

ext4 réserve une partie de ses blocs au compte root, afin qu’un disque plein n’empêche pas root de se connecter et de réparer la machine. Un processus exécuté par un utilisateur ordinaire atteint cette limite en premier, tandis que df indique encore un peu d’espace disponible. Consultez le paramètre de votre propre système de fichiers au lieu de supposer la valeur par défaut.

dev=$(df --output=source / | tail -n 1)
sudo tune2fs -l "$dev" | grep -i 'block count'

Cette commande affiche le nombre total de blocs et le nombre de blocs réservés dans les mêmes unités. Leur ratio est donc directement calculable. df indique dans la colonne available l’espace qu’un utilisateur normal peut encore utiliser. C’est pourquoi la somme de used et de available est inférieure à la taille totale. L’écart correspond à la réserve.

Modifiez ce paramètre avec sudo tune2fs -m <percent> "$dev". La modification s’applique immédiatement et ne nécessite aucun remontage. Réduire la réserve sur un système de fichiers dédié aux données est raisonnable. Sur le système de fichiers racine, conservez une réserve suffisante pour que root puisse encore écrire, car un système de fichiers racine totalement dépourvu d’espace libre est beaucoup plus difficile à réparer. tune2fs fonctionne avec ext2, ext3 et ext4. XFS ne propose pas de paramètre équivalent.

Quand du vous induit en erreur utilisé seul

Quatre comportements de du produisent des totaux qui semblent incorrects.

  • Liens physiques : du ne compte un inode qu’une seule fois, même lorsque plusieurs noms pointent vers celui-ci. Une arborescence remplie de liens physiques affiche donc un total inférieur à la somme de ses fichiers.
  • Fichiers clairsemés : du indique les blocs réellement alloués, tandis que ls -l indique la taille apparente. Ajoutez --apparent-size pour afficher l’autre valeur.
  • Permissions : exécuté par un utilisateur standard, du ignore ce qu’il ne peut pas lire et sous-évalue le total. Les erreurs qu’il affiche sont celles que l’on redirige vers /dev/null avant de ne plus les consulter.
  • Limites des systèmes de fichiers : sans -x, du / compte tous les systèmes de fichiers montés sous /. Son total peut donc dépasser celui indiqué par df /.

df a également un comportement qu’il est utile de connaître. Il indique séparément chaque système de fichiers. Exécutez-le donc sur le chemin exact ciblé par l’écriture qui échoue. Un système de fichiers /boot distinct se remplit selon son propre rythme à mesure que les paquets du noyau s’accumulent, et supprimer les anciens noyaux sur Ubuntu est une tâche différente du nettoyage de l’espace sur /.

Ordre d’intervention pour un incident réel

  1. Exécutez df -h <path> et df -i <path> sur le système de fichiers qui devait recevoir l’écriture en échec, et non systématiquement sur /.
  2. Exécutez sudo du -xh -d 1 <mountpoint> 2>/dev/null | sort -h, puis descendez dans le répertoire le plus volumineux.
  3. Si du n’explique pas l’espace que df indique comme utilisé, recherchez dans /proc les fichiers supprimés qui sont encore ouverts.
  4. Si les deux résultats concordent, montez le système de fichiers ailleurs avec bind mount et recherchez les fichiers situés sous un point de montage.
  5. Si le nombre d’inodes est la limite atteinte, comptez les fichiers plutôt que les octets.

Chaque étape repose sur une commande dont vous pouvez lire la sortie. C’est ce qui permet de corriger le problème au lieu de procéder au hasard.

FAQ

Pourquoi df indique-t-il que le disque est plein alors que du trouve beaucoup moins d’espace utilisé ?

La cause habituelle est un fichier supprimé alors qu’un processus le gardait encore ouvert. La suppression retire l’entrée du répertoire. du ne trouve donc plus de nom à parcourir et cesse de le comptabiliser. L’inode et ses blocs restent alloués jusqu’à la fermeture du dernier descripteur, tandis que df comptabilise les blocs alloués. Recherchez dans /proc/<pid>/fd les liens symboliques dont la cible est marquée comme supprimée : vous obtiendrez ainsi le fichier et le processus qui le garde ouvert. Avant de vous fier à la comparaison, vérifiez que vous avez exécuté du en tant que root et avec -x, car un utilisateur ordinaire ignore silencieusement les répertoires qu’il ne peut pas lire.

Comment trouver un fichier supprimé qui est encore ouvert sans lsof ?

Utilisez l’inventaire des descripteurs ouverts tenu par le kernel. sudo find /proc/[0-9]*/fd -lname '*(deleted)' -printf '%p -> %l\n' 2>/dev/null liste chaque descripteur qui pointe vers un fichier sans nom, et l’identifiant du processus apparaît dans le chemin affiché. sudo stat -Lc %s sur l’un de ces chemins de descripteur indique sa taille. Vous pouvez ainsi les trier et sélectionner celui qui vous intéresse. Aucun package n’est nécessaire, ce qui est important, car l’installation d’un package sur un système de fichiers sans espace libre peut échouer.

Puis-je libérer l’espace sans arrêter le processus ?

Parfois. sudo truncate -s 0 /proc/<pid>/fd/<n> atteint le même inode via le descripteur et libère ses blocs pendant que le processus continue de fonctionner. Cette méthode est la plus propre lorsque le processus a ouvert le fichier en mode append, car ses écritures vont toujours à la fin actuelle du fichier. Sinon, la position d’écriture reste à l’endroit où elle se trouvait. L’écriture suivante recrée alors le fichier avec un trou au début : la taille indiquée augmente de nouveau, tandis que les blocs correspondant au trou restent libres. Redémarrer l’unité, ou lui envoyer le signal indiqué par sa documentation pour rouvrir ses journaux, est la solution qui ne laisse aucun fichier sparse.

df indique de l’espace libre, mais les écritures échouent toujours. Quelle peut être la cause ?

Vérifiez les inodes avec df -i sur le même chemin, car un système de fichiers qui dispose de blocs libres mais d’aucun inode libre refuse les nouveaux fichiers. Vérifiez si l’écriture est exécutée par un utilisateur non-root sur un système de fichiers ext4 auquel il ne reste que les blocs réservés. sudo tune2fs -l sur le device l’indiquera. Vérifiez que vous consultez bien le système de fichiers réellement ciblé par l’écriture, car un /boot ou un /var distinct peut être plein indépendamment de /.

Pourquoi du indique-t-il un total supérieur à df ?

du sans -x traverse tous les systèmes de fichiers montés sous le chemin fourni. Il additionne donc plusieurs systèmes de fichiers, tandis que df décrit un seul système de fichiers. Les bind mounts aggravent le problème, car les mêmes fichiers sont comptabilisés une fois sous chaque chemin où ils apparaissent. Ajoutez -x pour limiter du à un seul système de fichiers, et donnez à df le même chemin afin que les deux commandes décrivent la même chose.