Сервер лагает. Смотришь на диск - df -h говорит 95% занято. Запускаешь du -sh /* - в сумме набирается 20%. Куда делись остальные 75%? Файлы не найти, место не освободить, сервис падает.
Это не баг и не магия. Это фундаментальная особенность того как Linux работает с файлами. Разберём почему так происходит и как это чинить за две команды.
Почему df и du показывают разное
df и du смотрят на файловую систему с разных сторон.
df читает метаданные файловой системы напрямую - сколько блоков выделено, сколько свободно. Это данные суперблока, они обновляются мгновенно при любом изменении.
du обходит дерево каталогов и суммирует размеры блоков которые видит. Ключевое слово - видит. du считает только то что доступно через файловую систему прямо сейчас.
Вот тут и начинается расхождение.
Файловый дескриптор и почему rm не удаляет файл сразу
В Linux файл - это не просто запись в каталоге. У каждого файла есть inode - структура в ядре которая хранит метаданные: права, владелец, размер, и главное - список блоков на диске где лежат данные.
Когда процесс открывает файл, ядро создаёт файловый дескриптор - ссылку на inode. Пока хотя бы один дескриптор на inode открыт, ядро не трогает блоки на диске.
Когда делаешь rm файл - удаляется только запись в каталоге (hardlink). Inode и блоки остаются нетронутыми пока счётчик ссылок не упадёт до нуля. Если процесс держит файл открытым - счётчик не упадёт.
Схема выглядит так:
каталог inode блоки на диске /var/log/ --> app.log --> [блок1][блок2][блок3] ^ | открытый дескриптор процесса nginx (pid 1234, fd 7) после rm: каталог inode блоки на диске /var/log/ --> (удалено) --> [блок1][блок2][блок3] ^ ^ | дескриптор | всё ещё занято! nginx (pid 1234, fd 7)
du обходит /var/log/ - файла там больше нет, не считает. df смотрит на блоки - они заняты, считает.
Отсюда расхождение.
Классический сценарий
Самый частый случай - ротация логов через rm.
Представь: logrotate настроен удалять старый лог и создавать новый. Он делает rm /var/log/app.log - запись из каталога пропала. Но nginx или java открыл этот файл на запись при старте и держит дескриптор. Процесс продолжает писать в удалённый файл, блоки заполняются, df видит рост, du ничего не находит.
Это может продолжаться часами и гигабайтами пока не кончится место или не перезапустят процесс.
Как найти виновника
$ lsof +L1
lsof - утилита которая показывает все открытые файлы в системе. Флаг +L1 означает "показать файлы у которых счётчик ссылок меньше 1" - то есть файл удалён из файловой системы, но процесс его ещё держит.
Вывод будет примерно такой:
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NLINK NODE NAME nginx 1234 www 7w REG 8,1 2147483648 0 12345 /var/log/app.log (deleted) java 5678 app 3w REG 8,1 536870912 0 67890 /var/log/service.log (deleted)
Разбираем колонки:
COMMAND - имя процесса
PID - идентификатор процесса
FD - номер дескриптора и режим доступа. 7w - номер 7, режим w (write). Буква означает что делает процесс: r читает, w пишет, u читает и пишет
SIZE/OFF - размер файла. Вот они, потерянные гигабайты
NLINK - счётчик ссылок. 0 означает что файл удалён из всех каталогов
NAME - путь с пометкой (deleted)
Теперь ясно: nginx пишет в удалённый лог размером 2 ГБ.
Как починить без рестарта процесса
Рестарт nginx освободит дескриптор и блоки. Но рестарт не всегда возможен - активные соединения, продакшен, согласования.
Есть способ освободить место не трогая процесс:
$ > /proc/1234/fd/7
Разбираем по частям:
>- оператор перенаправления в bash. Открывает файл на запись и сразу обнуляет его содержимое. Именно обнуляет - не удаляет, а делает размер ноль/proc/- это procfs, псевдофайловая система Linux. Не хранит данные на диске - представляет информацию о процессах и ядре в виде файлов и каталогов прямо в памяти/proc/1234/- каталог процесса с pid 1234fd/- подкаталог со всеми открытыми дескрипторами процесса в виде симлинков на реальные файлы7- номер дескриптора из вывода lsof
Команда идёт к файлу напрямую через дескриптор - минуя файловую систему где файл уже "удалён". Ядро обнуляет блоки, df сразу покажет освободившееся место. Процесс продолжает работать и писать в тот же дескриптор.
Проверить результат:
$ lsof +L1 | grep nginx # пусто или SIZE/OFF = 0
Как не попасть снова
Корень проблемы - logrotate использует rm + создание нового файла. Процесс продолжает писать в старый дескриптор.
Правильное решение - copytruncate в конфиге logrotate:
/var/log/app.log { daily rotate 7 compress copytruncate }
copytruncate работает иначе: копирует содержимое лога в архив, затем обнуляет оригинальный файл через тот же механизм что мы использовали вручную. Процесс продолжает писать в тот же файл, дескриптор не меняется, блоки освобождаются.
Минус: между копированием и обнулением есть небольшое окно где несколько строк лога могут потеряться. Для большинства случаев это приемлемо.
Если потеря строк недопустима - процесс должен поддерживать SIGHUP для переоткрытия лога. nginx умеет: nginx -s reopen. Тогда logrotate делает mv старого файла, создаёт новый, и посылает сигнал процессу.
Мониторинг
Чтобы не ловить это в 3 ночи - добавь проверку в мониторинг:
# удалённые но открытые файлы больше 100 МБ lsof +L1 -F s | awk '/^s/ && substr($0,2)+0 > 104857600 {count++} END {print count+0}'
-F s включает вывод размера в машиночитаемом формате - каждая строка выглядит как s2147483648. /^s/ фильтрует строки с размером. substr($0,2) убирает префикс s и оставляет число. +0 > 104857600 - больше 100 МБ (104857600 байт).
Или через Prometheus + node_exporter - метрика node_filefd_allocated показывает количество открытых дескрипторов. Резкий рост при падении свободного места - признак именно этой проблемы.
Итого
df и du расходятся когда файл удалён но процесс держит открытый дескриптор
rm не освобождает блоки пока счётчик ссылок не ноль
найти виновника:
lsof +L1починить без рестарта:
> /proc/<pid>/fd/<fd>не попасть снова: copytruncate в logrotate или SIGHUP после ротации
Механика одна и та же на любом Linux - от Raspberry Pi до серверов с терабайтными дисками.
Больше про Linux, DevOps и SRE - в Telegram-канале @b4shninja
Комментарии (24)

FSA
31.03.2026 07:18А я как-то привык писать логи в journald. Он, и логи в сжатом виде хранит (с поиском по ним), и о ротации заботится, и чтобы диск не забивался логами (ограничение на максимальный размер). А ещё du себя очень странно ведёт на дисках с btrfs. У самой утилиты обслуживания btrfs есть свой
btrfs filesystem du.
b4shninja Автор
31.03.2026 07:18journald - да, хороший вариант, приложение может просто писать в поток и не знать вообще ни про какой файл = не держать fd, вся ротация и прочее будет разруливаться на уровне journald.
btrfs - да, есть такой момент у btrfs, поэтому лучше отдельной тулзой для btrfs - вы правы.

andreymal
31.03.2026 07:18логи в сжатом виде хранит
Как заставить их сжиматься? У меня они занимают в 3 раза больше места чем текстовые (включенные по умолчанию Compress=yes и compact mode видимо бесполезны)

FSA
31.03.2026 07:18Скорее всего где-то вы ошиблись. Внимательно посмотрите на то, что там пишется. Может быть какой-то сервис сильно много чего-то пишет. В любом случае, если у вас journald, то там есть ограничение на максимальный объём файлов журнала. В случае переполнения, старые логи просто начнут удаляться и дополнительного места на диске уже не потребуется. Я бы просто запустил
journalctl -fи посмотрел чего у меня там летит такого, что постоянно забивает логи. Возможно у какого-то сервиса не выключена отладка или что-то сбоит.
andreymal
31.03.2026 07:18Это не имеет никакого значения, если тот же самый много пишущий сервис, пишущий в обычный текстовый файл вместо journald, станет занимать в 3 раза меньше места. Вышеупомянутый перенос логов nginx из текстовых файлов в journald для меня выглядит самоубийством (и это я ещё даже не вспоминал про то, что logrotate сожмёт текстовые логи после ротации)

FSA
31.03.2026 07:18Вышеупомянутый перенос логов nginx из текстовых файлов в journald для меня выглядит самоубийством
Рекомендую всё-таки ознакомиться с возможностями journalctl. Там есть и свой grep, и фильтр по датам, и поиск по метаданным, и аналог
tail -f.... Лично мне больше понравилось. Плюс не нужно быть root, чтобы логи смотреть. Достаточно дать пользователю необходимые права. А также вместо путей /var/log… можно пользоваться просто тегами или именами сервисов (опять таки, надо смотреть что удобнее в каждом конкретном случае). Мне тоже, по началу, казалось сложно, но сейчас наоборот - ведение логов в текстовых файлах кажется дичью.
andreymal
31.03.2026 07:18Возможности это конечно прикольно, но вот лично я не готов платить за них трёхкратным увеличением занятого места
аналог
tail -f...С лагом в одну секунду, что при реалтаймовой отладке чего-нибудь раздражает
Плюс не нужно быть root, чтобы логи смотреть.
Достаточно быть в группе adm

FSA
31.03.2026 07:18С лагом в одну секунду, что при реалтаймовой отладке чего-нибудь раздражает
Ради интереса открыл journalctl -f на своём компьютере и попробовал обратиться к веб-серверу nginx локальному. За одно туда много пишет отладочной информации php-fpm, всё-таки у меня сервер стоит для разработки. Время от нажатия кнопки обновить в браузере и вывод логов занимает доли секунды. Точнее, чтобы узнать сколько, нужно что-то аппаратное, потому что глазом видно, что это происходит почти мгновенно.
Вероятно на продакшене может быть задержка, Но она связана, скорее всего, с повышением эффективности логирования. Если нагрузка большая и логов много, логично накапливать их в оперативной памяти, а потом сбрасывать на диск всем куском, чем дёргать диск на каждый чих.

andreymal
31.03.2026 07:18А впрочем чего я теоретизирую, если могу провести эксперимент в виртуалке: настроил nginx access_log одновременно на файл и на syslog (чтобы ушло в journald), налил 100 тысяч запросов — /var/log/journal занял 96МБ (логи заранее почистил, чтобы в них был только nginx), текстовый access.log занял 27МБ (и может быть сжат gzip'ом до 4МБ)

b4shninja Автор
31.03.2026 07:18верное замечание, access.log действительно лучше не держать в journald, как раз таки из за оверхеда, сам journald добавляет еще кучу полей к записи.

andy_p
31.03.2026 07:18du обходит дерево каталогов и суммирует размеры файлов
du суммирует размер не файлов, а занятых блоков. Поэтому если у вас много мелких файлов, то du насчитает намного больше, чем сумма их длин.

vesper-bot
31.03.2026 07:18но всё равно не больше чем df, так же?

b4shninja Автор
31.03.2026 07:18Верно.
df - сколько блоков занято на устройстве с точки зрения файловой системы, включая всё что du никогда не увидит (метаданные ФС, зарезервированные блоки, удалённые но открытые файлы)
du - сколько блоков числится за файлами которые он обошёл

astenix
31.03.2026 07:18Когда процесс открывает файл, ядро создаёт… ссылку на inode
Открывает в смысле «создает» новый файл?

b4shninja Автор
31.03.2026 07:18Когда процесс открывает файл ядро делает три вещи:
1. Находит inode - для существующего файла идёт по пути через directory entries. Для нового (O_CREAT) - сначала создаёт inode, потом directory entry на него.
2. Создаёт struct file - это объект в памяти ядра: указатель на inode, текущая позиция в файле (offset), флаги (O_RDONLY и т.д.). Это и есть "ссылка на inode".
3. Кладёт fd в таблицу процесса - файловый дескриптор (число 3, 4, 5...) это просто индекс в таблице который указывает на struct file.
RedEyedAnonymous
Звучит как откровенное вредительство.
FSA
Это не вредительство, это обычное поведение файловой системы linux. Оно в корне отличается от того, что происходит в Windows. Пока что-то держит файл, файл после rm физически не удаляется и продолжает работать. При этом для тех, кто файл не держал, файл фактически считается удалённым. В Windows же при попытке удалить файл, пока его кто-то держит, вываливается ошибка.
Всех этих мучений, что описаны в статье, можно было бы избежать, если бы сервис не писал файлы логов самостоятельно, а доверил это какому-либо сервису по ведению логов. Там все особенности файловой системы linux уже учтены.
NickDoom
…со стороны «nginx или java» таки вредительство, потому что надо или понимать особенности, или «доверить это какому-либо сервису по ведению логов».
А то перекомпилировали, завелось — и радуются. А эта особенность даже домохозяйкам-на-убунте уже совершенно понятна и во многом кажется логичнее (особенно если они начинали с убунты и не выработали синдром утёнка к мастдаю).
FSA
Могу сказать только в защиту nginx. Он по умолчанию пишет сообщения самостоятельно. Но это легко меняется в настройках и он отлично пишет в journald. Особенно удобно, когда логи nginx можно просматривать совместно с логами php-fpm через journalctl. Сразу видно какой запрос пришёл, какую отладочную информацию выдал php-fpm и какие проблемы возникли при обработке запроса PHP. В java, наверно, тоже можно сделать что-то подобное.
NickDoom
На фоне питона они вообще все ангелы… вот уж кто считает, что не он для системы, а система для него. А равно и юзеры, и разрабы…
Особенно юзерам «весело». Каждый первый проект с гитхаба требует или полностью разобраться в использованном стеке просто для того, чтобы это всё завелось под конкретной версией, или взять отдельный ноут и циклически форматировать диск, ставя с нуля систему и под ней перебирая версии питона — авось заведётся. Где он так откладывает личинки, что даже sudo apt purge не помогает — хз.
Ну да, люди же писали, трудились, а ты хочешь, чтобы без труда всё у тебя заработало, ага. Научись из речного песка свой процессор делать, тогда удостоишься /s
Anselm_nn
Я бы скорее вредительством назвал поведение в windows, которое не дает удалить файл, даже от админа, если кто-то его держит. В древние времена была тулза Unlocker, которая могла сказать "УДАЛЯЙ" и удалялось. Давно не сижу на windows и было как-то не очень важно, вот сейчас решил спросить у gemini, оказывается Unlocker и аналоги еще живы (правда сначала он впаривал какие-то инструменты посмотреть кем открыто и его закрывать)
Скрытый текст
TL;DR: Вы абсолютно правы. Официальные современные утилиты слишком «вежливые» — они заставляют вас разбираться, кто держит файл, и вручную убивать процессы. Если вам нужен подход старого Unlocker (нажал правой кнопкой $\rightarrow$ «Удалить любой ценой» $\rightarrow$ файла нет), вам нужны сторонние утилиты, использующие драйверы уровня ядра для принудительного отрыва дескрипторов. Лучшие из них сейчас — IObit Unlocker, DeadLock и сам оригинальный Unlocker 1.9.2.
Ниже приведен подробный разбор инструментов, которые работают по принципу «вижу цель — не вижу препятствий», и объяснение того, как именно они ломают защиту Windows.
1. Программы для бескомпромиссного удаления (Аналоги старого Unlocker)
Эти инструменты не предлагают вам «подумать» или «завершить задачу». Они делают грязную работу сами: либо жестко убивают процесс-владелец, либо вырывают у него дескриптор файла на уровне ядра.
IObit Unlocker
Как работает: Добавляет пункт в контекстное меню. При выборе открывается окно, где есть кнопка-стрелка рядом с «Unlock». Выбираете «Unlock & Delete» (Разблокировать и удалить).
Результат: Программа автоматически сбрасывает дескрипторы и удаляет файл. Никаких вопросов.
Нюанс: Это коммерческий продукт, но базовая функция разблокировки бесплатна. Как и со всем софтом от IObit, при установке нужно внимательно снимать галочки, чтобы не поставить лишний рекламный мусор.
DeadLock
Как работает: Бесплатная утилита, созданная именно для агрессивного снятия блокировок. Также встраивается в Проводник.
Результат: Позволяет в пару кликов сменить владельца файла (Take Ownership) и принудительно удалить его (Unlock and Delete), даже если система сопротивляется.
LockHunter (Режим быстрого удаления)
Хотя я упоминал его ранее, стоит уточнить: в нём не обязательно смотреть на список процессов. Там есть большая кнопка «Delete It!».
Результат: Нажатие этой кнопки автоматически закроет хэндлы или убьет процессы, мешающие удалению, и отправит файл в корзину (или удалит навсегда, если зажать Shift).
Оригинальный Unlocker (от Cedrick Collomb)
Реальность: Тот самый легендарный Unlocker (версия 1.9.2) всё ещё работает даже на Windows 10 и 11.
Нюанс: Проект заброшен много лет назад. Его официальный сайт мертв. Скачивать приходится со сторонних зеркал (например, MajorGeeks).
Предупреждение: Последние версии оригинального инсталлятора содержали рекламное ПО (Delta Toolbar). Браузеры и Защитник Windows почти гарантированно пометят его как PUA/PUP (потенциально нежелательное ПО). Если решите ставить — ищите портативную версию (Portable) или внимательно снимайте галочки при установке.
2. Deep Dive: Как работает "Магия" агрессивного удаления и почему её нет в Windows
Ваше возмущение («раз я удаляю файл, значит работа с ним полностью неактуальна») логично с точки зрения пользователя. Но архитектура Windows (NTFS) выстроена иначе.
Чтобы программа могла просто «взять и удалить» заблокированный файл, она должна применить один из двух методов глубокого системного вмешательства:
Метод А: Принудительное закрытие дескриптора (Forced Handle Closure)
Когда программа (например, Word или фоновый системный процесс) открывает файл, ядро Windows выдает ей «дескриптор» (handle) — уникальный номер. В таблице объектов ядра ставится отметка, что файл заблокирован.
Утилиты вроде Unlocker устанавливают свой собственный драйвер режима ядра (ring-0). Этот драйвер:
Находит в оперативной памяти таблицу дескрипторов чужого процесса.
Вызывает недокументированные системные функции (например,
NtClose) от имени системы.Жестко удаляет запись о том, что файл занят.
Риски: Программа-владелец не знает, что у неё отобрали файл. В следующую миллисекунду она пытается записать данные по старому адресу (дескриптору), который теперь указывает в никуда или на другой системный объект. Это вызывает исключение (Exception). Если файл держал системный драйвер или антивирус, это мгновенно приводит к синему экрану (BSOD)
IRQL_NOT_LESS_OR_EQUAL.Метод Б: Отложенное удаление при загрузке (Boot-time Deletion)
Если дескриптор оторвать нельзя (например, файл занят самим ядром ОС), агрессивные утилиты используют API-функцию
MoveFileExс флагомMOVEFILE_DELAY_UNTIL_REBOOT.Она записывает путь к файлу в специальный ключ реестра
PendingFileRenameOperations. При следующей перезагрузке, еще до того, как загрузятся сервисы и графический интерфейс (до появления экрана блокировки), встроенный менеджер сеансов Windows (smss.exe) читает этот ключ и физически удаляет файл с диска.Итог: Microsoft никогда не добавит кнопку «Удалить без вопросов» в саму Windows, потому что Метод А нарушает фундаментальную стабильность ОС и гарантии целостности данных, которые дает файловая система NTFS. Однако, как технически грамотный пользователь, вы можете использовать инструменты с драйверами уровня ядра (IObit Unlocker или старый Unlocker), принимая риск того, что программа, державшая файл, в этот момент крашнется.
ReadOnlySadUser
Microsoft нынче разрабатывает PowerToys. Это эдакий швейцарский нож из ништяков, без которых мне уже и винда - не винда. Там есть аналог unlocker
Anselm_nn
Не аналог, а жалкая породия, именно это сначала гемини и предложил. Там показывается, какие процессы обращаются к файлу, типо или и сам их ищи закрывай. Это и близко не удалить принудительно