Диск заполнен: как найти и освободить место на Linux-сервере
Если df показывает 100 процентов, а du ничего не находит: полный путь от измерения до освобождения места, для Debian 12 и 13, а также Ubuntu 22.04 и 24.04.
Заполненный накопитель редко предупреждает заранее. Обычно первым падает какой-нибудь сервис: MariaDB пишет в лог OS error 28, nginx отдаёт 500, резервное копирование обрывается с write error: No space left on device, а в самом неудачном случае на машину уже никто не заходит по SSH, потому что sshd не может создать файлы сессии. В этой статье разобран весь путь: измерить, найти, освободить, проверить. Включая те два случая, на которых большинство инструкций заканчивается, а именно когда df показывает переполнение, а du ничего не находит.
Сначала измерить: какой раздел вообще заполнен?
Прежде чем что-то удалять, нужно понять, какая файловая система пострадала. У заполненного /boot совсем другие причины, чем у заполненного /var.
df -h
df -hT -x tmpfs -x devtmpfs -x squashfs
Вторая строка скрывает псевдофайловые системы. На Ubuntu это особенно полезно, потому что там каждое установленное snap-приложение появляется как отдельная точка монтирования squashfs через loop и засоряет вывод. Кстати, такие loop-монтирования всегда заполнены на 100 процентов, это нормально и проблемой не является.
Важна колонка Mounted on. Типичные ситуации на серверах:
| Точка монтирования | Обычная причина переполнения |
|---|---|
| / | логи, Docker, кэш apt, данные приложений |
| /boot | старые ядра и их образы initramfs |
| /var | журнал, rsyslog, почтовая очередь, базы данных, Docker |
| /tmp | оборванные загрузки, сессии, остатки сборок |
Ещё одна деталь, которая часто сбивает с толку: ext4 по умолчанию резервирует пять процентов ёмкости для пользователя root. Сервис, работающий от www-data или mysql, получит No space left on device уже в тот момент, когда root в ту же секунду ещё спокойно пишет на диск. На чисто файловых разделах (то есть не на корневой файловой системе) этот резерв можно безопасно уменьшить:
tune2fs -m 1 /dev/sdb1
На / резерв лучше оставить. Это ровно та подушка, за счёт которой переполненную систему вообще ещё можно починить.
Как пользоваться du и не заблудиться
Классический подход: спускаться по одному уровню за раз и всегда с -x:
du -xh --max-depth=1 / | sort -h
du -xh --max-depth=1 /var | sort -h
-x — самый важный ключ во всей статье. Он удерживает du в пределах одной файловой системы и не даёт команде провалиться в /proc, /sys, смонтированные сетевые ресурсы или диски с резервными копиями. Без -x поиск идёт минутами и выдаёт цифры, которые к переполненному разделу отношения не имеют. sort -h правильно сортирует размеры в человекочитаемом виде, поэтому самый крупный кусок оказывается внизу.
Кому удобнее кликать интерактивно, тот ставит ncdu и запускает его тоже с -x:
apt-get install -y ncdu
ncdu -x /
Отдельные крупные файлы быстрее найти напрямую:
find /var -xdev -type f -size +100M -exec ls -lh {} +
Здесь регулярно возникают два подводных камня, которые ведут к неверным выводам. Первый: du считает занятые блоки, а не логический размер файла. У разрежённых файлов (табличные пространства баз данных, образы виртуальных дисков) эти два значения расходятся очень сильно. Сравнение делает разницу видимой:
du -sh /var/log
du --apparent-size -sh /var/log
Второй: du считает жёсткие ссылки только один раз. Тот, кто работает не от root, дополнительно получает строки вида du: cannot read directory '/var/lib/private': Permission denied и вместе с ними систематически заниженные суммы. Поэтому весь анализ следует вести от root или через sudo.
Ограничить журнал systemd
Журнал — самый частый тихий пожиратель места на серверах. По умолчанию ему разрешено занимать десять процентов файловой системы с потолком в четыре гигабайта, и дополнительно он держит свободными 15 процентов файловой системы. На диске объёмом 500 гигабайт это до четырёх гигабайт чистого лога.
journalctl --disk-usage
Здесь есть реальное различие между дистрибутивами, о котором многие инструкции умалчивают. Попадёт ли журнал на диск вообще, при настройке по умолчанию Storage=auto зависит исключительно от того, существует ли каталог /var/log/journal:
ls -d /var/log/journal
ls -d /run/log/journal
На минимальных установках Debian и во многих облачных образах Debian 12 и Debian 13 каталога /var/log/journal нет. Журнал тогда лежит в /run/log/journal, то есть в RAM, исчезает при каждой перезагрузке и диск не нагружает вовсе. Зато он нагружает оперативную память. Ubuntu Server 22.04 и 24.04, наоборот, обычно создают этот каталог и пишут журнал на диск постоянно. Проверять, а не угадывать.
Освободить место немедленно:
journalctl --rotate
journalctl --vacuum-size=200M
journalctl --vacuum-time=7d
Вызов --rotate перед этим не украшение: опции vacuum удаляют только уже заархивированные файлы журнала и никогда не трогают активный. Если основную часть объёма занимает как раз активный файл, то без предварительной ротации внешне не происходит ничего, и именно на этом спотыкаются читатели, копирующие команду из форумного поста.
Ограничить навсегда через отдельный файл, чтобы будущие обновления пакетов ничего не перезаписали:
mkdir -p /etc/systemd/journald.conf.d
printf '[Journal]\nSystemMaxUse=200M\nRuntimeMaxUse=50M\n' > /etc/systemd/journald.conf.d/00-size.conf
systemctl restart systemd-journald
Проверка, что это действительно подействовало: journalctl --disk-usage теперь обязан показывать меньшее значение, а df -h обязан показывать больше свободного места. Если размер журнала уменьшается, а df остаётся прежним, значит, какой-то процесс до сих пор держит открытыми уже удалённые файлы. Об этом ниже.
Второе различие между дистрибутивами: на классических серверных установках Debian и Ubuntu параллельно часто работает ещё и rsyslog, который пишет те же сообщения второй раз в /var/log/syslog. В минимальных образах Ubuntu (облако, контейнер) rsyslog, наоборот, отсутствует. Проверяется командой ls -l /var/log/syslog. Если файл существует и он огромный, то проблема не в журнале, а в отсутствующем или сломанном правиле logrotate в /etc/logrotate.d/.
Кэш apt и старые ядра
Скачанные пакеты остаются лежать на диске после установки. На долго работающем сервере это быстро превращается в несколько гигабайт.
du -sh /var/cache/apt
apt-get clean
du -sh /var/lib/apt/lists
apt-get clean полностью очищает /var/cache/apt/archives, а apt-get autoclean удаляет только те пакеты, которых больше нет в источниках. Чего clean не трогает, так это списки пакетов в /var/lib/apt/lists. При большом числе подключённых источников они дорастают до нескольких сотен мегабайт и безопасно пересоздаются:
rm -rf /var/lib/apt/lists/*
apt-get update
Вторая строка здесь не необязательное дополнение, а обязанность, причём немедленная. Между удалением списков и следующим apt-get update apt не знает ни одного пакета: любой apt-get install в этом состоянии обрывается с E: Unable to locate package ... и кодом возврата 100, хотя пакет в источниках, разумеется, есть.
Вторая классика — старые ядра. Ubuntu через unattended-upgrades постоянно ставит новые ядра, но убирает старые только тогда, когда это явно разрешено. Каждое ядро вместе с initramfs занимает около 100-150 мегабайт в разделе /boot, размер которого часто составляет всего 512 мегабайт или один гигабайт.
uname -r
dpkg -l 'linux-image-*'
apt-get autoremove --purge
Работающее ядро из uname -r при этом не удаляется никогда, как и самое новое. На Ubuntu подстраховываются тем, что прописывают в /etc/apt/apt.conf.d/50unattended-upgrades строку Unattended-Upgrade::Remove-Unused-Kernel-Packages "true";. На Debian 12 и 13 unattended-upgrades по умолчанию не активен, там /boot растёт только в том случае, если кто-то регулярно обновляет систему вручную и никогда не прибирается.
Если /boot уже заполнен и apt больше не отрабатывает
Вот случай, который по-настоящему болезнен. Типичные сообщения дословно:
update-initramfs: failed for /boot/initrd.img-6.8.0-60-generic with 1.
dpkg: error processing package linux-image-6.8.0-60-generic (--configure):
installed linux-image-6.8.0-60-generic package post-installation script subprocess returned error exit status 1
E: Sub-process /usr/bin/dpkg returned an error code (1)
Теперь база данных пакетов находится в наполовину готовом состоянии, и каждый следующий вызов apt спотыкается на том же самом месте. Выход, именно в такой последовательности:
- Записать вывод
uname -r. Эту версию не трогают ни при каких обстоятельствах. - Вывести
ls -lh /bootи найти самую старую версию из тех, что сейчас не работают. - Удалить только её
initrd.img-*, но неvmlinuz-*. Файл initramfs с большим отрывом самый крупный и создаётся заново в любой момент. - Выполнить
apt-get -f install, чтобы dpkg смог завершить прерванную настройку. - Только после этого
apt-get autoremove --purge, чтобы старые пакеты исчезли чисто и вместе с записью в GRUB. - Запустить
update-grubи прочитать вывод.
Чего делать не стоит: удалять командой rm файлы ядер из /boot наугад, а остальное игнорировать. dpkg тогда по-прежнему считает пакеты установленными, GRUB предлагает пункты меню, которые ведут в пустоту, и следующая перезагрузка заканчивается в аварийной консоли GRUB. Кто уже успел удалить, возвращает согласованность через apt-get install --reinstall затронутого пакета или через dpkg --purge, после чего обязателен update-grub.
Проверка: df -h /boot снова показывает свободное место, dpkg -l 'linux-image-*' перечисляет всего две или три записи со статусом ii, а вывод update-grub называет ровно те ядра, которые действительно лежат в /boot.
Docker, Snap и логи контейнеров
На хостах с Docker ответ почти всегда лежит в /var/lib/docker. Не гадать, а спросить:
docker system df
docker system df -v
Подробный вариант аккуратно разделяет образы, контейнеры, тома и кэш сборки. После этого прибираться прицельно:
docker image prune -a
docker builder prune
docker system prune -a
Слово об осторожности с ключом --volumes: он удаляет и те тома, к которым не привязан ни один запущенный контейнер. Кто держит базу данных в именованном томе и как раз остановил контейнер, потеряет вместе с этим данные. Без свежей резервной копии команде docker system prune -a --volumes на продуктивном сервере делать нечего.
Недооценённая статья расхода — логи контейнеров в /var/lib/docker/containers/*/*-json.log. Драйвер по умолчанию json-file без явного указания не ротирует их вообще. Разговорчивый контейнер за месяцы записывает так десятки гигабайт в один-единственный файл. Средство против этого в /etc/docker/daemon.json:
{
"log-driver": "json-file",
"log-opts": { "max-size": "50m", "max-file": "3" }
}
После этого systemctl restart docker. Внимание, и об этом почти нигде не пишут: настройка действует только на вновь создаваемые контейнеры. Существующие сохраняют свою старую конфигурацию до тех пор, пока их однажды не пересоздадут, в случае Compose через docker compose up -d --force-recreate.
Версии Docker здесь заметно различаются. Debian 12 поставляет docker.io 20.10, Debian 13 приносит 26.1, Ubuntu 22.04 и 24.04 дошли тем временем до 29.1. Чем новее версия, тем больше места занимает кэш BuildKit и тем важнее docker builder prune, который один только docker system prune не всегда очищает полностью.
На Ubuntu добавляется Snap. Старые ревизии остаются лежать отключёнными и продолжают занимать место:
snap list --all
snap set system refresh.retain=2
Значение 2 — это минимум, который принимает snapd, меньшие значения отклоняются с сообщением об ошибке. В Debian никакого Snap нет, там этот раздел отпадает без замены.
df показывает переполнение, du ничего не находит
Теперь интересный случай. df -h показывает 100 процентов, а сумма по du даёт лишь половину. Для этого есть ровно четыре правдоподобные причины.
Удалённые, но всё ещё открытые файлы
Это с большим отрывом самая частая причина. Кто-то выполнил rm /var/log/riesig.log, пока сервис ещё держал файл открытым. Запись в каталоге исчезла, поэтому du больше ничего не видит. Блоки остаются занятыми до тех пор, пока не закрыт последний файловый дескриптор, поэтому df продолжает их учитывать.
apt-get install -y lsof
lsof +L1
В колонке NLINK тогда стоит 0, а после пути указано (deleted). Если lsof ничего не нашёл, вывода нет, а код возврата равен 1, и это не ошибка. Без lsof можно обойтись и напрямую через файловую систему процессов:
ls -l /proc/*/fd 2>/dev/null | grep deleted
Важно: выполнять обязательно от root. Обычный пользователь видит только собственные дескрипторы и пропускает тем самым ровно те системные сервисы, которые в девяти случаях из десяти и оказываются виновниками.
Чистый способ освободить место — перезапустить сервис, например systemctl restart rsyslog. Если перезапуск невозможен, файл можно урезать до нулевой длины через его дескриптор. Идентификатор процесса и номер дескриптора берутся из вывода lsof:
truncate -s 0 /proc/1234/fd/7
Это немедленно освобождает блоки, процесс после этого продолжает писать. Метод предназначен исключительно для лог-файлов, открытых в режиме дозаписи. К файлам баз данных, образам виртуальных машин и вообще ко всему с произвольным доступом применять его нельзя никогда, там он приводит к потере данных.
По чему видно, что получилось: df -h сразу показывает больше свободного места, а lsof +L1 больше не выводит эту запись. Повторный прогон du, наоборот, не изменится совсем, ведь там файл и раньше был невидим. Именно в этом и причина того, что перезагрузка сервера будто бы волшебным образом решает проблему.
Файлы, скрытые под точкой монтирования
Классика: кто-то записал данные в /mnt/backup ещё до того, как туда был примонтирован настоящий диск. Данные так и лежат на корневой файловой системе, но закрыты сверху точкой монтирования. Увидеть это можно только через второй взгляд на ту же самую файловую систему:
mkdir -p /mnt/rootview
mount --bind / /mnt/rootview
du -xh --max-depth=2 /mnt/rootview | sort -h
umount /mnt/rootview
Bind-монтирование безопасно, оно ничего не перевешивает и снимается обратно командой umount.
Резерв для root и ошибка прав доступа
Уже упомянутые пять процентов резерва ext4 объясняют разрыв между «ещё не совсем заполнено» и «сервисы уже падают». И наконец: кто запускает du не от root, целых деревьев каталогов не видит. Мнимая разница тогда попросту оказывается проблемой прав. На btrfs и ZFS дополнительным объяснением могут быть снимки, которые du тоже никогда не показывает.
Когда заканчивается не место, а inode
Существует второй вид «переполнения», который выдаёт ровно то же сообщение об ошибке. Каждый файл и каждый каталог занимает один inode, а их количество в ext4 задаётся раз и навсегда в момент форматирования.
df -i
stat -f /
Если в df -i колонка IUse% показывает 100, а df -h при этом сообщает о достатке свободного места, диагноз однозначен: у сервера проблема не с местом, а с количеством. Миллионы крошечных файлов израсходовали все inode. Сообщение об ошибке при этом всё равно No space left on device, и именно поэтому большинство ищет не там.
Виновников находят, считая файлы, а не байты:
find /var -xdev -printf '%h\n' | sort | uniq -c | sort -rn | head -20
Частые кандидаты: почтовая очередь в /var/spool/postfix или /var/spool/exim4, сессии PHP в /var/lib/php/sessions, каталоги кэша веб-приложений, вынесенные зависимости Node и никогда не очищаемый /tmp.
При самом удалении поджидает следующее препятствие. rm /var/lib/php/sessions/* при очень большом числе файлов падает с bash: /usr/bin/rm: Argument list too long, потому что командная строка выходит за предел по размеру. Способ, который работает всегда:
find /var/lib/php/sessions -type f -mtime +7 -delete
Что нужно знать: количество inode у существующей файловой системы ext4 задним числом увеличить нельзя. Помогает только увеличение самой файловой системы (при этом количество inode растёт пропорционально) либо её пересоздание с более плотным размещением, например через mkfs.ext4 -i 8192 /dev/sdb1. Кто заведомо работает с очень большим числом мелких файлов, например на почтовых серверах или в архивах изображений, тому лучше подойдёт XFS, потому что XFS выделяет inode динамически и практически заканчивается только тогда, когда заканчивается и место. Debian и Ubuntu по умолчанию форматируют в ext4, XFS нужно выбирать сознательно.
Проверить и сделать так, чтобы это не повторилось
Уборка считается успешной только тогда, когда сходятся три вещи: df -h показывает больше свободного места, df -i показывает снижение занятости inode, и сервис, который изначально упал, снова работает. Команда, отработавшая без сообщений об ошибке, сама по себе ещё не доказательство. Особенно journalctl --vacuum-size и docker system prune охотно и молча не делают ровным счётом ничего.
Для постоянной эксплуатации оправдывает себя короткий список: жёстко ограничить журнал через SystemMaxUse, задать ротацию логов Docker в daemon.json, на Ubuntu включить Remove-Unused-Kernel-Packages, охватить собственные логи приложений через /etc/logrotate.d/ и завести простое cron-задание, которое отправляет письмо при превышении порога. На KVM-серверах и выделенных серверах вдобавок имеет смысл вынести /var или хотя бы /var/log на отдельный раздел. Тогда разбушевавшийся лог парализует один сервис, но не операционную систему, и на машину в любом случае ещё можно будет зайти по SSH.
Частые вопросы
Почему df показывает 100 процентов, хотя du находит заметно меньше?
Сколько места может занимать журнал systemd и как ограничить его навсегда?
Журнал в Debian и Ubuntu лежит одинаково?
/boot заполнен, а apt обрывается с ошибкой dpkg. Что делать?
Безопасна ли команда "docker system prune -a --volumes"?
Что делать, если df -i показывает inode занятыми на 100 процентов?
2026 KernelHost GmbH. Все права защищены. Эта инструкция охраняется авторским правом. Публикация на других сайтах, в том числе частично или в изменённом виде, без нашего письменного согласия не разрешается. Цитирование с указанием источника и активной ссылкой мы приветствуем.

