SSD Nodes Learn Hosting plans →
Руководства Matt ConnorАвтор: Matt Connor · Обновлено 2026-08-25

Почему df показывает диск заполнен, а 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) запрашивает у каждой смонтированной файловой системы её собственные данные: сколько блоков существует, сколько выделено и сколько свободно. Утилита никогда не открывает каталоги. Ответ включает каждый выделенный блок, включая блоки, принадлежащие файлу, на который не указывает ни одна запись в каталоге.

du (disk usage) делает обратное. Она начинает работу с указанного вами пути, читает каталоги, выполняет stat для каждой найденной записи и суммирует блоки. Файл без имени для неё невидим. То же самое касается любого каталога, к которому утилита не имеет доступа, поэтому обычный пользователь получает меньший итоговый результат, чем root. Запустите du от имени sudo, прежде чем делать какие-либо выводы на основе сравнения.

При сравнении этих двух утилит всегда важны две опции.

  • -x удерживает du в пределах одной файловой системы. Без этой опции du / переходит в каждую файловую систему, смонтированную внутри /, и выдает итоговое значение, которое df / никогда не учитывала.
  • -s выводит одну итоговую строку для каждого аргумента вместо одной строки для каждого каталога.

Это дает пару команд для одновременного запуска на нужной вам файловой системе.

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

df отвечает мгновенно. du на большой файловой системе может потребоваться несколько минут, так как утилита выполняет stat для каждого файла на пути. Если итоговые значения сильно различаются, а 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 .

$(...) — это подстановка команд: оболочка выполняет команду внутри, а вывод становится значением 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 & запускает фоновый процесс, стандартным вводом которого является этот файл, поэтому оболочка открывает файл и передает дескриптор в sleep, который удерживает его открытым. $! содержит идентификатор процесса (PID) этого фонового задания. rm затем удаляет имя файла, пока дескриптор все еще открыт.

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

Поиск процесса, удерживающего удаленный файл

Каждый открытый файловый дескриптор отображается в /proc/<pid>/fd/ как символическая ссылка на файл, к которому он относится. Когда файл удален (unlinked), ядро помечает цель этой ссылки как удаленную. Таким образом, поиск владельца сводится к поиску ссылки, цель которой имеет этот маркер.

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

-lname сопоставляет цель символической ссылки, а не ее имя, %p выводит путь к дескриптору, а %l показывает, на что он указывает. Идентификатор процесса (PID) является вторым элементом пути, который выводит команда. Запускайте ее с 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 является универсальным.

Освобождение места без перезагрузки

Перезагрузка действительно решает проблему, но это неверный первый шаг: она останавливает сервис и уничтожает улики. Существует четыре более щадящих способа, расположенных в порядке их применения.

Сначала скопируйте данные, если они вам еще нужны. Чтение по пути дескриптора позволяет считать содержимое активного 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 mode), так как каждая запись в этом случае идет в текущий конец файла. Если это не так, процесс сохраняет старое смещение записи, поэтому следующая запись попадет далеко вглубь файла и воссоздаст его с «дырой» в начале. Дыра не занимает место на диске, поэтому блоки остаются свободными, а df сохраняет только что освобожденное пространство. Восстанавливается только размер: если запустить sudo stat -L "/proc/$pid/fd/$n" снова после записи процесса, он покажет старый размер рядом с количеством блоков, которое ему уже не соответствует. Перезапустите процесс, если хотите, чтобы размер также начал отсчитываться с нуля.

Третий способ — попросить сервис переоткрыть логи. Демон, чей лог-файл был удален из-под него, — распространенный реальный пример этой проблемы. Многие демоны переоткрывают лог-файлы при получении сигнала: nginx использует SIGUSR1, а rsyslog — SIGHUP. Проверьте документацию для вашего демона вместо того, чтобы угадывать, так как неверный сигнал, отправленный не тому демону, может его остановить.

sudo systemctl kill -s USR1 nginx

Эта команда отправляет сигнал процессу, который systemd зарегистрировал как основной для юнита. Поэтому юнит, в котором указан неверный параметр Type= для способа запуска демона, может доставить ваш сигнал процессу, который никогда не удерживал удаленный файл, и место останется занятым.

Четвертый способ — перезапуск юнита. 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 и root du -x показывают одинаковые данные, значит, дело не в удаленных файлах. Оставшиеся причины имеют иную природу, и для каждой из них предусмотрена своя проверка.

Исчерпание inodes при наличии свободного места

Inode содержит метаданные одного файла. Файловая система ext4 создает фиксированное количество inodes при форматировании, поэтому место в ней может закончиться из-за нехватки inodes, даже если свободные блоки еще есть. Создание новых файлов будет завершаться ошибкой, хотя df -h показывает наличие свободного пространства.

df -h /
df -i /

Первая команда подсчитывает блоки, а вторая — inodes. Сравните столбец использования (use) для каждой из них. Низкий уровень использования блоков при достижении лимита inodes означает, что проблема вызвана огромным количеством очень маленьких файлов.

df не позволяет использовать -i и --output в одном вызове, поэтому, если вам нужны «сырые» значения для чтения или передачи другой команде, выбирайте поля inodes по имени и не указывайте -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 для медленного подсчета поддерева.

Решение заключается в удалении или перемещении этих файлов. Вы не можете добавить inodes в уже существующую файловую систему ext4, так как их количество фиксируется во время mkfs, поэтому увеличение лимита требует пересоздания файловой системы и восстановления из резервной копии. XFS выделяет inodes по мере необходимости, поэтому она не сталкивается с таким жестким ограничением. Сервер, на котором работают контейнеры, достигает обоих лимитов быстрее остальных, так как слои образов содержат множество мелких файлов. Для такой машины очистка дискового пространства 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, чтобы переполнение диска не препятствовало входу в систему и её восстановлению. Процесс, запущенный от имени обычного пользователя, столкнётся с нехваткой места раньше, в то время как df всё ещё будет показывать небольшой запас. Проверьте текущую настройку вашей файловой системы, вместо того чтобы полагаться на значения по умолчанию.

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

Эта команда выводит общее количество блоков и количество зарезервированных блоков в одних и тех же единицах измерения, поэтому их соотношение можно вычислить напрямую. df отображает в столбце доступного места объём, который может использовать обычный пользователь; именно поэтому сумма занятого и доступного места меньше общего размера. Разница — это и есть резерв.

Измените его с помощью sudo tune2fs -m <percent> "$dev". Изменения вступают в силу немедленно и не требуют перемонтирования. Уменьшение резерва на отдельной файловой системе для данных вполне оправдано. На корневой файловой системе оставьте достаточно места, чтобы root мог записывать данные, так как полностью заполненную корневую файловую систему гораздо сложнее восстановить. Это также то, что отделяет вас от потери доступа: ключ, добавленный в authorized_keys на файловой системе, где не осталось свободного места, может быть записан не полностью или не записан вовсе, и при следующей попытке входа вы получите Permission denied (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 не находит имени для обхода и перестает учитывать этот файл. Инод и его блоки остаются выделенными до тех пор, пока не будет закрыт последний дескриптор, а df учитывает именно выделенные блоки. Ищите в /proc/<pid>/fd символические ссылки, цель которых помечена как удаленная: так вы найдете и сам файл, и процесс, который его удерживает. Прежде чем доверять результатам сравнения, убедитесь, что вы запустили du от имени root и с флагом -x, так как обычный пользователь без предупреждения пропускает каталоги, к которым у него нет доступа на чтение.

Как найти удаленный файл, который все еще открыт, без использования lsof?

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

Можно ли освободить место, не завершая процесс?

Иногда. sudo truncate -s 0 /proc/<pid>/fd/<n> обращается к тому же иноду через дескриптор и освобождает его блоки, пока процесс продолжает работу. Это наиболее безопасно, если процесс открыл файл в режиме добавления (append mode), так как запись всегда идет в конец файла. Если это не так, смещение записи остается на прежнем месте, и следующая операция записи воссоздает файл с «дырой» в начале. В результате сообщаемый размер файла восстанавливается, хотя блоки под «дырой» остаются свободными. Решением, которое не оставляет разреженных файлов, является перезапуск юнита или отправка ему сигнала для повторного открытия логов (сигнал должен быть указан в документации к приложению).

df показывает свободное место, но запись все равно не удается. В чем еще может быть причина?

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

Почему du сообщает о большем общем объеме, чем df?

du без флага -x переходит в каждую файловую систему, смонтированную внутри указанного пути, поэтому команда суммирует данные нескольких файловых систем, в то время как df описывает только одну. Ситуацию усугубляют bind-монтирования, из-за которых одни и те же файлы учитываются несколько раз для каждого пути, где они отображаются. Добавьте -x, чтобы ограничить du одной файловой системой, и укажите df тот же путь, чтобы обе команды описывали один и тот же объект.