SSD Nodes Learn Hosting plans →
Посібники Matt ConnorВід Matt Connor · Оновлено 2026-08-28

df показує 100%, а du не знаходить місце

Розберіть помилку, коли df показує заповнений диск, а du ні: знайдіть видалений файл, який утримує процес, через lsof, без перезавантаження.

Verified Every command ran end-to-end on a fresh Ubuntu 24.04 server, August 15, 2026.

Чому df показує заповнений диск, а du — ні

df показує, що диск заповнений, а du не знаходить зайнятого місця, оскільки процес досі утримує видалений файл. Видалення файла прибирає його ім’я з каталогу. Блоки даних звільняються лише після закриття останнього відкритого дескриптора файла, що вказує на цей inode. du обходить імена, тому нічого не підраховує. df запитує у файлової системи кількість виділених блоків, тому й далі враховує файл, який уже не має імені.

У цьому посібнику описано, як відтворити проблему на звичайному Ubuntu VPS за допомогою вже встановлених інструментів, знайти процес, що утримує файл, через /proc і звільнити місце без перезавантаження. Далі розглянуто інші причини такого самого симптому: таблиця inode без вільних записів, файли, приховані під точкою монтування, і блоки, зарезервовані для root.

Виконуйте кожну команду та перевіряйте власний вивід. Значення залежать від вашого диска, тому порівнюйте стан до і після на власній машині, а не з цифрою з посібника.

Що підраховують df і du

df (disk free) звертається до кожної змонтованої файлової системи та використовує її власну статистику: скільки блоків існує, скільки виділено та скільки вільно. df не відкриває каталоги. Результат охоплює кожен виділений блок, зокрема блоки файлу, на який не вказує жоден запис каталогу.

du (disk usage) працює навпаки. Він починає з указаного вами шляху, читає каталоги, отримує статистику для кожного знайденого запису та підсумовує блоки. Файл без імені для нього невидимий. Так само невидимим буде каталог, який йому заборонено читати. Тому звичайний користувач отримує менший підсумок, ніж root. Запустіть du через sudo, перш ніж робити висновки з порівняння.

Під час кожного порівняння важливі два параметри.

  • -x залишає du в межах однієї файлової системи. Без нього du / переходить у кожну файлову систему, змонтовану під /, і формує підсумок, який df / взагалі не підраховував.
  • -s виводить один підсумковий рядок для кожного аргументу, а не окремий рядок для кожного каталогу.

Тому на потрібній файловій системі слід запускати цю пару команд паралельно.

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

df одразу виводить результат. du може виконуватися кілька хвилин у великій файловій системі, оскільки під час проходження отримує статистику для кожного файлу. Якщо два підсумки істотно відрізняються, а du запущено від імені root із -x, відсутній простір зайнятий об’єктом, який не має імені.

Навмисно відтворіть невідповідність

Виконайте це на тестовому VPS. Усе нижче використовує bash і coreutils, тому нічого встановлювати не потрібно.

Зафіксуйте початковий стан файлової системи, у якій міститься /var/tmp.

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

Друга команда виводить використані байти без округлення. Тому підсумкова перевірка буде точною.

Тепер створіть файл. Його розмір визначається на основі вільного простору, який сама машина повідомляє, тому приклад працює з будь-яким диском.

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

$(...) — це підстановка команд: shell виконує команду всередині неї, а її вивід стає значенням free. Якщо цей синтаксис для вас новий, у матеріалі підстановка команд у bash він належно описаний. fallocate резервує реальні блоки без запису даних, тому команда завершується миттєво. У файловій системі, яка цього не підтримує, команда завершується помилкою, а head -c $((free / 10)) /dev/zero > ghost.bin виконує ту саму роботу, записуючи байти.

Порівняйте цей df -h . із результатом, який ви записали. Значення у стовпці використаного простору зросло, а значення у стовпці доступного простору зменшилося.

Тепер відкрийте файл в іншому процесі, а потім видаліть його.

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

Уся суть у перенаправленні. sleep infinity < ghost.bin & запускає фоновий процес, стандартним входом якого є цей файл. Тому shell відкриває файл і передає файловий дескриптор процесу sleep, який залишає його відкритим. $! зберігає ідентифікатор процесу цього фонового завдання. Потім rm видаляє ім’я файлу, доки дескриптор залишається відкритим.

Перегляньте вивід. ls не може знайти файл, оскільки його ім’я видалено. du повернувся приблизно до початкового значення, оскільки він обходить імена. df не змінився, оскільки блоки все ще виділені. Тепер файлові блоки та дерево каталогів не узгоджуються, а розрив між ними — це файл, який ви щойно видалили.

Знайдіть процес, який утримує видалений файл

Кожен відкритий файловий дескриптор з’являється в /proc/<pid>/fd/ як symbolic link на файл, на який він посилається. Якщо файл видалено, kernel позначає ціль цього посилання як deleted. Тому потрібно знайти посилання, ціль якого містить цю позначку.

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

-lname зіставляє ціль symbolic link, а не його ім’я, %p виводить шлях до дескриптора, а %l показує, на що він вказує. ID процесу — це другий елемент виведеного шляху. Запускайте команду з sudo, інакше ви зможете читати лише /proc/<pid>/fd власних процесів. Перенаправлення stderr прибирає повідомлення від процесів, які завершуються під час обходу find.

На завантаженому сервері в будь-який момент можуть утримуватися кілька видалених файлів. Більшість із них невеликі й безпечні. Відсортуйте їх за розміром, щоб цікаві файли опинилися на початку списку.

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 переходить за посиланням безпосередньо до inode, тому %s виводить розмір файлу, який більше не має імені. Сортування за цим числом ставить найбільший файл на перше місце.

Потім визначте процес, який відповідає за потрібний дескриптор. У верхньому рядку списку є обидва потрібні числа. Спочатку запишіть їх у змінні, замінивши PID і N значеннями з власного виведення.

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

ps називає програму та показує, як довго вона працює. stat -L виводить розмір і кількість виділених блоків видаленого inode. Разом ці дані відповідають на головне запитання: який сервіс утримує цей файл відкритим.

Якщо на машині вже є lsof, sudo lsof +L1 виводить відкриті файли, для яких лічильник посилань зменшився до нуля, і показує їхні розміри в одній таблиці. У мінімальному образі Ubuntu цього пакета немає. Встановлення пакета у файлову систему без вільного місця також може завершитися помилкою, тому обхід за допомогою /proc — це варіант, який працює завжди.

Звільнення місця без перезавантаження

Перезавантаження справді усуває проблему, але це неправильний перший крок: сервіс стає недоступним, а докази проблеми зникають. Є 4 обережніші варіанти. Виконуйте їх у наведеному порядку.

Спочатку скопіюйте дані, якщо вони вам ще потрібні. Читання шляху до дескриптора читає актуальний inode.

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

Це єдиний випадок, коли видалений файл легко відновити. Саме тому відновлення файлів, видалених за допомогою rm -rf починається з перевірки, чи утримує процес файл відкритим. Коли останній дескриптор закривається, цей спосіб більше недоступний.

По-друге, очистьте файл через дескриптор. Шлях /proc веде до того самого inode, тому його усікання звільняє блоки, а процес продовжує працювати.

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

Це коректно працює, коли процес записує файл у режимі append, оскільки кожен запис тоді виконується в поточний кінець файлу. Якщо файл відкрито не в цьому режимі, процес зберігає стару позицію запису. Тому наступний запис потрапляє далеко за межі файлу та відновлює його з hole на початку. Для hole блоки не виділяються, тому вони залишаються вільними, а df зберігає звільнене місце. Змінюється лише розмір: після запису процесу знову виконайте sudo stat -L "/proc/$pid/fd/$n". Команда покаже старий розмір і кількість блоків, яка йому більше не відповідає. Перезапустіть процес, якщо потрібно, щоб розмір також починався з нуля.

По-третє, попросіть сервіс повторно відкрити журнали. Типовий реальний варіант цієї проблеми виникає, коли файл журналу демона видалено, але демон продовжує його утримувати. Багато демонів повторно відкривають файли журналів після отримання сигналу: nginx використовує SIGUSR1, а rsyslog — SIGHUP. Перевірте документацію потрібного демона, а не вгадуйте сигнал. Неправильний сигнал, надісланий неправильному демону, зупиняє його.

sudo systemctl kill -s USR1 nginx

Сигнал надсилається процесу, який systemd записує як головний процес unit. Тому unit із неправильним Type=, що не відповідає фактичному запуску демона може передати сигнал процесу, який ніколи не утримував видалений файл. Місце залишиться зайнятим.

По-четверте, перезапустіть unit. sudo systemctl restart <unit> закриває всі дескриптори, які утримував старий процес, тому блоки гарантовано повертаються у вільний стан. У наведеному прикладі дескриптор утримує sleep, який ви запустили самостійно, тому достатньо завершити цей процес.

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

Порівняйте використаний обсяг із значенням, яке ви записали до створення файлу. Значення знову збігаються, а find більше не показує ваш дескриптор. Варто перевіряти результат тією самою командою, яка виявила проблему.

Стежити за зміною цього значення простіше, ніж знову й знову вручну запускати df. watch повторює команду через фіксований інтервал і виводить результат на тому самому місці, тому watch df -h / показує, як змінюється стовпець використаного місця після його звільнення.

Коли підсумки збігаються, але диск усе ще заповнений

Якщо df і du -x для root узгоджуються між собою, видаленого файла немає. Причини, що залишилися, мають інший характер, і для кожної є окрема перевірка.

Не вистачає inode, а не блоків

inode містить метадані одного файлу. ext4 створює фіксовану кількість inode під час створення файлової системи, тому файловій системі може не вистачати inode, навіть якщо в ній ще є вільні блоки. Створення нових файлів завершується помилкою, хоча df -h показує вільне місце.

df -h /
df -i /

Перша команда підраховує блоки, а друга — inode. Порівняйте стовпець use в обох командах. Якщо використання блоків низьке, а використання inode досягло ліміту, проблема полягає у дуже великій кількості дуже малих файлів.

df відхиляє -i і --output в одному виклику. Тому, коли потрібно отримати необроблені значення для читання або передавання іншій команді, виберіть поля inode за іменем і не вказуйте -i.

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

Ці стовпці містять ті самі облікові дані, які виводить df -i, у форматі, придатному для подальшого аналізу.

Знайдіть файли, підраховуючи записи, а не байти.

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

Повторюйте ту саму команду на один рівень нижче в каталозі, який опинився першим у списку, доки не дійдете до дерева, у якому створюються файли. Якщо ваш du не підтримує --inodes, тоді sudo find /var -xdev -type f | wc -l повільно підраховує вміст піддерева.

Рішення полягає у видаленні або переміщенні цих файлів. Додати inode до наявної файлової системи ext4 неможливо, оскільки їхню кількість фіксують під час mkfs. Тому збільшення кількості inode потребує повторного створення файлової системи та відновлення даних із резервної копії. XFS виділяє inode за потреби, тому не досягає фіксованого ліміту таким самим чином. На машині, що запускає контейнери, обидва ліміти досягаються швидше, ніж зазвичай, оскільки шари образів містять багато малих файлів. На такій машині очищення дискового використання Docker на VPS є цільовим рішенням і звільняє значно більше місця, ніж загальний перегляд файлової системи.

Простір, прихований під точкою монтування

Каталог може містити файли ще до монтування будь-якої файлової системи в цей каталог. Змонтуйте файлову систему поверх цього каталогу — файли під нею залишаться саме там, де були: вони й далі займатимуть місце, враховуватимуться в df, але більше не будуть доступні за іменем. du не може їх побачити, оскільки монтування приховує їх.

Продемонструйте це за допомогою tmpfs, для якого не потрібен вільний простір на диску. Для цього потрібна машина, на якій вам дозволено монтувати файлові системи, тому приклад працює на KVM VPS.

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

У середньому ls показано порожній каталог. Копія нікуди не зникла: вона й далі міститься в кореневій файловій системі та знову стане доступною одразу після демонтування. Тепер уявіть сервіс, який протягом місяця записував журнали в цей шлях, перш ніж хтось змонтував поверх нього том.

Щоб знайти такі файли на запущеному сервері, змонтуйте кореневу файлову систему вдруге в іншому місці. Bind mount показує одну файлову систему без файлових систем, змонтованих усередині неї.

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

Усе, що є в цьому списку, але відсутнє за звичайним шляхом, приховане під точкою монтування. Після завершення роботи демонтуйте bind mount, інакше наступний du без -x двічі врахує ті самі файли.

Блоки, зарезервовані для root

ext4 резервує частину блоків для користувача root, щоб заповнений диск не завадив root увійти в систему й відновити її. Процес, що працює від звичайного користувача, досягає цього обмеження першим, тоді як df ще показує певний вільний простір. Перевірте це налаштування у власній файловій системі, а не покладайтеся на значення за замовчуванням.

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

Команда виводить загальну кількість блоків і кількість зарезервованих блоків в однакових одиницях, тому їхнє співвідношення можна визначити безпосередньо. df показує в колонці available простір, який іще може використати звичайний користувач. Саме тому сума використаного й доступного простору менша за загальний розмір. Різниця — це резерв.

Змініть це значення за допомогою sudo tune2fs -m <percent> "$dev". Зміна застосовується негайно, і повторне монтування не потрібне. Зменшити резерв на окремій файловій системі для даних цілком прийнятно. Для кореневої файлової системи залиште достатній резерв, щоб root і надалі міг записувати дані, оскільки кореневу файлову систему, у якій зовсім не залишилося вільного місця, значно складніше відновити. Цей резерв також не дає вам втратити доступ до системи: додавання ключа до authorized_keys на файловій системі, де більше немає місця, може завершитися неповним записом або взагалі не відбутися, а під час наступного входу система відповість Доступ заборонено (publickey) з причини, не пов’язаної із самим ключем. tune2fs працює з ext2, ext3 і ext4. В XFS еквівалентного налаштування немає.

Де du вводить в оману сам по собі

Чотири особливості du дають підсумки, які здаються неправильними.

  • Жорсткі посилання: du враховує inode лише один раз, навіть якщо на нього вказує кілька імен. Тому дерево, заповнене жорсткими посиланнями, показує менший розмір, ніж сума його файлів.
  • Розріджені файли: du показує фактично виділені блоки, а ls -l — видимий розмір. Додайте --apparent-size, щоб побачити інше значення.
  • Права доступу: якщо запустити команду від звичайного користувача, du пропустить те, що він не може прочитати, і покаже занижений результат. Повідомлення про помилки, які вона виводить, зазвичай перенаправляють у /dev/null, після чого їх перестають читати.
  • Межі файлових систем: без -x du / враховує кожну файлову систему, змонтовану всередині /. Тому його підсумок може перевищувати значення, яке показує df /.

df також має одну особливість, про яку варто знати. Вона показує кожну файлову систему окремо, тому запускайте її для точного шляху, до якого звертається операція запису з помилкою. Окремий /boot заповнюється за власним графіком у міру накопичення пакетів ядра, а видалення старих ядер в Ubuntu — це інше завдання, ніж звільнення місця в /.

Робочий порядок дій під час реального інциденту

  1. Виконайте df -h <path> і df -i <path> у файловій системі, для якої призначалася невдала операція запису, а не рефлекторно в /.
  2. Виконайте sudo du -xh -d 1 <mountpoint> 2>/dev/null | sort -h, а потім переходьте до найбільшого каталогу.
  3. Якщо du не пояснює, чому df показує використаний простір, перевірте /proc на наявність видалених файлів, які все ще відкриті.
  4. Якщо результати збігаються, змонтуйте файлову систему через bind mount в іншому місці та перевірте, чи немає файлів під точкою монтування.
  5. Якщо ліміт досягнуто через використання inode, підрахуйте файли, а не байти.

На кожному кроці є команда, результат якої можна прочитати. Саме це відрізняє усунення проблеми від припущень.

FAQ

Чому df показує, що диск заповнений, хоча du знаходить набагато менше даних?

Зазвичай причина полягає у файлі, який видалили, поки процес усе ще тримав його відкритим. Видалення файлу прибирає його запис із каталогу, тому du більше не має імені, за яким можна перейти, і припиняє його підраховувати. Inode та його блоки залишаються виділеними, доки не закриється останній дескриптор, а df підраховує виділені блоки. Виконайте пошук у /proc/<pid>/fd символічних посилань, ціль яких позначена як deleted, і знайдіть сам файл та процес, який його утримує. Перш ніж покладатися на порівняння, переконайтеся, що запустили du від імені root і з параметром -x, оскільки звичайний користувач без повідомлення пропускає каталоги, які не може прочитати.

Як знайти видалений файл, який усе ще відкритий, без lsof?

Скористайтеся власним переліком відкритих дескрипторів ядра. sudo find /proc/[0-9]*/fd -lname '*(deleted)' -printf '%p -> %l\n' 2>/dev/null перелічує всі дескриптори, що вказують на файл без імені, а ідентифікатор процесу міститься в шляху, який команда виводить. sudo stat -Lc %s для одного з таких шляхів дескриптора показує його розмір, тому можна відсортувати результати та вибрати потрібний файл. Для цього не потрібен жоден пакет. Це важливо, оскільки встановлення пакета у файловій системі без вільного місця може завершитися помилкою.

Чи можна звільнити місце, не завершуючи процес?

Іноді. sudo truncate -s 0 /proc/<pid>/fd/<n> звертається до того самого inode через дескриптор і звільняє його блоки, поки процес продовжує працювати. Це найкращий варіант, якщо процес відкрив файл у режимі append, оскільки записи завжди додаються в поточний кінець файлу. Якщо це не так, позиція запису залишається на попередньому місці, а наступний запис відтворює файл із hole на початку. Тому повідомлений розмір знову збільшується, а блоки під hole залишаються вільними. Перезапуск unit або надсилання йому сигналу для повторного відкриття журналів, який вказано в документації, усуває проблему без створення sparse-файлу.

df показує вільне місце, але запис усе одно завершується помилкою. У чому ще може бути причина?

Перевірте inode за допомогою df -i для того самого шляху, оскільки файлова система з вільними блоками, але без вільних inode не приймає нові файли. Перевірте, чи виконується запис від імені користувача без прав root у файловій системі ext4, де залишилися лише зарезервовані блоки. Це покаже sudo tune2fs -l для відповідного пристрою. Переконайтеся, що перевіряєте файлову систему, у яку фактично виконується запис, оскільки окремий /boot або /var заповнюється незалежно від /.

Чому du показує більший загальний обсяг, ніж df?

du без -x переходить у кожну файлову систему, змонтовану в межах указаного шляху. Тому команда підсумовує кілька файлових систем, тоді як df описує одну. Bind mounts погіршують ситуацію, оскільки ті самі файли враховуються окремо для кожного шляху, за яким вони доступні. Додайте -x, щоб обмежити du однією файловою системою, і передайте df той самий шлях, щоб обидві команди описували один і той самий об’єкт.