Почему df и du показывают разный размер — и кто из них врёт
Классическая ситуация: df -h кричит, что корневой раздел забит на 95%, а du -sh / после долгого прохода по дереву находит от силы половину этого объёма. Первая мысль — «du врёт» или «df врёт», и дальше начинается перебор случайных гипотез. На самом деле обе команды честны — они просто отвечают на разные вопросы. Разобравшись, на какие именно, вы за пару минут находите, куда делось место.
Содержание
Что вообще измеряют df и du
df не считает файлы. Она читает суперблок файловой системы — служебную структуру, где ядро хранит агрегированные счётчики: сколько блоков всего, сколько свободно, сколько занято. Это число обновляется на лету при каждой операции записи и удаления, и df просто выводит его — мгновенно, без обхода дерева каталогов. Отсюда и скорость: df -h отрабатывает за миллисекунды даже на разделе с миллионами файлов.
du устроена ровно противоположно. Она обходит дерево каталогов, начиная с указанной точки, и для каждого файла складывает размер, который тот реально занимает на диске (в блоках), — отсюда и медлительность на больших деревьях. Ключевое слово здесь — «дерево каталогов»: du видит только то, до чего может дойти через файловую иерархию, начиная с точки запуска. Если файл существует на диске, но нигде не виден по имени в дереве — du его попросту не найдёт, а df всё равно посчитает.
Из этого единственного различия механики вытекают почти все практические расхождения ниже. Ни одна из команд не «врёт»: df отвечает на вопрос «сколько занято физически», du — на вопрос «сколько занимают файлы, которые я вижу в дереве».
Удалённые, но открытые файлы — самая частая причина
Это причина номер один в проде, и она же самая контринтуитивная. В Unix rm не освобождает место немедленно — она убирает запись о файле из каталога (unlink). Если на файл в этот момент есть открытый файловый дескриптор у какого-то процесса, блоки данных остаются занятыми на диске до тех пор, пока этот дескриптор не закроется. Файла больше нет ни в одном ls, ни в выводе du — а df по-прежнему видит занятое место, потому что суперблок ничего не освободил.
Типичный сценарий: сервис пишет лог-файл, вы (или logrotate без правильного copytruncate, или сам сервис) удаляете этот файл напрямую, а процесс продолжает писать в уже отвязанный inode — не подозревая, что имени у файла больше нет. Файл растёт часами или днями, du -sh /var/log показывает скромные цифры, а df неумолимо приближается к 100%.
Находится это командой lsof:
lsof +L1
Флаг +L1 — фильтр именно на «файлы со счётчиком ссылок меньше 1», то есть удалённые, но всё ещё открытые. Вывод покажет PID процесса, имя команды и размер файла в столбце SIZE/OFF. Более узкий вариант, если подозреваете конкретный процесс:
lsof -p <PID> | grep deleted
Или без привязки к PID — искать по всей системе:
lsof / | grep deleted
Как только видите виновника — решение простое: перезапустить процесс (systemctl restart <service>), и он откроет новый файл лога с нуля, а старые блоки освободятся сразу при закрытии дескриптора. Если перезапускать сервис нельзя (продакшн без окна на рестарт), можно обнулить сам файл через дескриптор процесса, не трогая его работу:
: > /proc/<PID>/fd/<FD>
где <FD> — номер дескриптора из вывода lsof (столбец FD). Это безопаснее рестарта: процесс продолжает писать в тот же открытый файл, просто с нулевого размера, и никакого простоя нет.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать VPSSparse-файлы: диск занят меньше, чем кажется по размеру
Здесь расхождение работает в обратную сторону — файл может «весить» гигабайты по номинальному размеру, но занимать на диске гораздо меньше. Это sparse-файлы: файловая система не выделяет блоки под нулевые (незаписанные) участки файла, а просто помнит, что там пусто, и отдаёт нули при чтении. Типичные примеры — образы виртуальных машин (qcow2, raw), файлы подкачки, некоторые дампы баз данных, файлы, созданные через truncate или dd seek=.
ls -lh показывает логический размер файла — то, что «видно» приложению. du по умолчанию считает реально выделенные блоки. На sparse-файле эти числа разойдутся:
ls -lh disk.img
du -h disk.img
Если хотите заставить du считать как ls — то есть логический размер вместо реально выделенного — есть флаг:
du --apparent-size -h disk.img
Обратный случай тоже бывает: сравнение du без флагов и с --apparent-size на одном и том же дереве — быстрый способ понять, сколько места вам реально экономят sparse-файлы (актуально для дисков виртуалок и снапшотов).
Hardlinks: один и тот же файл под разными именами
Жёсткая ссылка (hardlink) — это не копия файла, а второе имя для того же inode, то есть для тех же блоков данных на диске. Если du обходит дерево и встречает два имени, указывающих на один inode, по умолчанию (GNU du) она посчитает данные один раз, а не дважды — она достаточно умна, чтобы не задваивать. Но при обходе с других точек, или инструментами, которые не отслеживают уже виденные inode, суммарный размер может отличаться от ожидаемого — особенно если ссылки раскиданы по разным подкаталогам, которые вы суммируете вручную по отдельности.
Практический пример — инструменты бэкапа с ротацией через hardlinks (классическая схема rsync --link-dest, на которой держится немалая часть бэкап-скриптов): десять «снапшотов» директории, каждый выглядит как полная копия, а реально на диске лежит один набор данных плюс дельты. du -sh по каждому снапшоту отдельно даст завышенную на бумаге картину; du -sh по всей директории со снапшотами — верную. Проверить, сколько у файла ссылок и не hardlink ли это, можно так:
stat -c '%n: links=%h, inode=%i' файл
Если links больше 1 — где-то в системе есть ещё как минимум одно имя для тех же самых блоков.
Разные точки монтирования путают суммы
df всегда группирует по файловым системам (разделам, точкам монтирования). du, если её не ограничивать, спокойно уходит с текущей точки монтирования на вложенные — например, вы считаете du -sh /, а внутри примонтирован отдельный раздел под /var/log или сетевая шара под /mnt/data, и du без предупреждения включает их содержимое в общую сумму.
Получается обратная путаница: du -sh / показывает больше, чем реально занято на корневом разделе по df -h /, потому что часть суммы — это данные с другого раздела, который просто примонтирован внутри дерева. Чтобы du не пересекала границы файловых систем и считала честно только «этот» раздел, используется флаг -x:
du -x --max-depth=1 -h / | sort -hr
Сверить список смонтированных разделов и на каком именно из них реально не хватает места удобно через:
findmnt -D
Она покажет ту же информацию, что и df, но с явной привязкой к точкам монтирования — источник и цель сразу видны в одной таблице, что особенно полезно на серверах с несколькими дисками или сетевыми хранилищами. Если у вас несколько разделов под разные сервисы, вопрос «сколько диска реально заложить с запасом» стоит решать заранее — эта тема отдельно разобрана в статье про объём диска с запасом.
Зарезервированные блоки для root
Ещё одна причина, по которой обычный пользователь видит один процент занятости, а df под root — другой: файловые системы семейства ext (ext2/ext3/ext4) по умолчанию резервируют часть блоков раздела исключительно для root. Исторически это делалось, чтобы у системы всегда оставался запас на случай, если раздел заполнит непривилегированный процесс — root должен суметь зайти и почистить диск, даже если для «обычных» пользователей места формально уже нет.
Классическое значение резерва — 5% от объёма раздела, но это настраиваемый параметр, а не жёсткая константа, и на конкретном сервере он может быть уменьшен или вовсе обнулён при создании ФС. Посмотреть текущее значение:
tune2fs -l /dev/sdX1 | grep -i reserved
Изменить (уменьшить резерв, если раздел большой, а запас в процентах избыточен — например, на разделе с данными, а не системном):
sudo tune2fs -m 1 /dev/sdX1
Здесь -m 1 устанавливает резерв в 1% вместо стандартных 5%. На маленьких системных разделах трогать это не стоит — резерв там как раз и спасает от полной блокировки системы при внезапном заполнении диска. На больших разделах под данные (десятки-сотни гигабайт) 5% резерва — это уже заметный объём в абсолютных числах, и его снижение может быть оправдано. Обратите внимание: у df резервированные блоки обычно не показываются как «занято» напрольную пользователю, но df -h без sudo может показать раздел заполненным на 100%, тогда как реально свободные (но зарезервированные под root) блоки ещё есть — отсюда расхождение между тем, что видит обычный пользователь, и тем, что видит root.
Как быстро найти виновника: чек-лист
Когда df и du расходятся, а разбираться в причине хочется не гадая, а по шагам, вот последовательность, которая закрывает почти все случаи из разделов выше:
# 1. Смотрим общую картину по разделам
df -h
# 2. Ищем открытые, но удалённые файлы — самая частая причина
lsof +L1
# 3. Если ничего не нашли — сравниваем du с ключом -x (без пересечения разделов)
# и с --apparent-size (без учёта sparse-экономии), с du без флагов
du -x --max-depth=1 -h / | sort -hr
du -x --apparent-size --max-depth=1 -h / | sort -hr
# 4. Проверяем резерв root, если раздел ext4 и разница именно в процентах для root/non-root
tune2fs -l /dev/sdX1 | grep -i reserved
# 5. Проверяем точки монтирования, если подозреваете смешение разделов
findmnt -D
Порядок неслучаен: удалённые открытые файлы — причина в большинстве обращений «диск полон, а du ничего не находит», поэтому lsof +L1 идёт вторым шагом сразу после общего осмотра. Если она ничего не дала — переходите к sparse-файлам и hardlinks, это уже более редкие, но не экзотические случаи, особенно на серверах с виртуалками, базами данных или бэкапами через hardlink-схемы.
Если после всего этого место всё равно не находится, а du -x подтверждает, что данных реально мало, — стоит проверить, не забит ли диск не файлами, а inode-таблицей (актуально для разделов с миллионами мелких файлов): df -i покажет процент занятых inode отдельно от занятых блоков, это уже другая история и другая диагностика. Если проблема стабильно повторяется — логично не бороться с симптомом раз в неделю, а настроить мониторинг диска, который поймает нарастание задолго до 100%, и заодно разобраться с ротацией логов, чтобы удалённые-но-открытые файлы логов не накапливались в принципе.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать VPSНужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.
Частые вопросы
Может ли du показывать больше, чем df?
Да, если du считает данные с примонтированных внутри поддиректорий разделов (без флага -x) или суммирует hardlinks некорректно при ручном сложении нескольких вызовов du по отдельным подкаталогам. Сам df при этом честно показывает занятость только своего раздела.
Почему после удаления большого файла место не освободилось сразу?
Скорее всего, файл всё ещё открыт каким-то процессом — проверьте lsof +L1 или lsof | grep deleted. Место освободится, когда процесс закроет дескриптор (обычно при перезапуске).
Безопасно ли перезаписывать файл через /proc/<PID>/fd/<FD>?
Да, это стандартный и безопасный способ обнулить лог без перезапуска сервиса — процесс продолжает писать в тот же дескриптор, просто с чистого листа. Убедитесь только, что взяли правильный PID и FD из свежего вывода lsof, а не из старого.
Стоит ли уменьшать зарезервированные 5% на ext4?
На системном разделе — как правило нет, резерв там подстраховка от полной блокировки при заполнении диска. На отдельном разделе под данные, где резерв в процентах превращается в заметный объём, снижение через tune2fs -m обычно оправдано.
Как понять, что дело в inode, а не в блоках?
df -h показывает свободное место в байтах, df -i — свободные inode отдельно. Если блоков полно, а df -i показывает 100% использования inode, значит проблема в количестве файлов, а не в их суммарном весе — типично для директорий с миллионами мелких файлов (кэши, сессии, временные файлы).
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →