Каждый системный администратор и вебмастер хотя бы раз сталкивался с критической аварией: боевой сервер перестает отвечать, базы данных MySQL или PostgreSQL аварийно останавливаются, а консоль Linux на любую команду выдает фатальную ошибку No space left on device. При этом сайты не содержат терабайтов медиафайлов, а свободное дисковое пространство VDS за несколько месяцев незаметно «съели» текстовые логи веб-сервера Nginx, Docker-контейнеров и системного демона journald.
В этом практическом руководстве мы подробно разберем, как работает штатная утилита Logrotate в Ubuntu 24.04 и Debian 12, как безопасно сжимать и удалять старые логи Nginx без перезагрузки процессов, как справиться с приложениями в режиме copytruncate и как раз и навсегда ограничить аппетиты Docker daemon и systemd-journald.
1. Почему неконтролируемые логи забивают диск и как устроен Logrotate
На активном веб-сервере Nginx под нагрузкой даже 15–20 запросов в секунду генерирует около 1.3–1.7 миллиона строк в access.log ежедневно. При стандартном формате комбинированного лога (директива combined, где средняя длина записи с заголовками User-Agent и Referer составляет 180–240 байт) это создает порядка 250–400 МБ несжатого текста в сутки. Без настроенной ротации за 3 месяца один только журнал доступа вырастает до 25–35 ГБ, полностью блокируя корневой раздел небольшого виртуального сервера.
Типичная ошибка начинающего администратора — выполнить команду rm /var/log/nginx/access.log, заметив переполнение диска. Файл пропадает из вывода ls, но утилита df -h продолжает показывать 100% заполненность диска!
В Linux пространство освобождается только тогда, когда процесс закрывает файловый дескриптор. Пока рабочий процесс Nginx держит дескриптор открытым, ядро продолжает удерживать блоки данных на диске. Найти такие «зависшие» удаленные файлы можно командой:
# Поиск процессов, удерживающих удаленные файлы логов в памяти
sudo lsof +L1 | grep deleted
# Пример вывода:
# nginx 1420 www-data 4w REG 252,1 15843219456 0 135168 /var/log/nginx/access.log (deleted)
Чтобы корректно ротировать файлы без блокировки процессов, потери поступающих HTTP-запросов и зависших дескрипторов, в Linux используется утилита Logrotate. Её алгоритм работы состоит из четырех строгих этапов:
| Шаг | Действие Logrotate | Результат в файловой системе |
|---|---|---|
| 1. Переименование | Текущий access.log переименовывается в access.log.1 |
Inode файла сохраняется, демон продолжает писать в переименованный файл |
| 2. Создание нового файла | Создается пустой access.log с исходными правами (create 0640 www-data adm) |
Готов целевой файл для переключения |
| 3. Оповещение демона | Выполняется скрипт postrotate с отправкой POSIX-сигнала USR1 мастер-процессу |
Nginx закрывает старый дескриптор и открывает новый access.log без рестарта |
| 4. Сжатие архивов | Файлы старше одного цикла сжимаются утилитой gzip в формат .gz (экономия 85–92% места) |
Файлы старше лимита (например, rotate 14) удаляются |
Подробнее о безопасных методах поиска пожирателей диска читайте в нашей статье как безопасно освободить место на диске Linux VDS.
2. Службы и таймеры systemd: как Logrotate запускается в Linux
Вопреки распространенному заблуждению, в современных дистрибутивах Linux (Ubuntu 22.04 LTS, Ubuntu 24.04 LTS, Debian 12 Bookworm) утилита Logrotate запускается не через классический демон cron, а с помощью системного таймера systemd.
Таймер logrotate.timer активирует однократную службу logrotate.service один раз в сутки. Проверить статус таймера и время следующего запланированного запуска можно следующей командой:
# Проверка активности таймера ротации логов
systemctl status logrotate.timer
# Просмотр точного расписания всех системных таймеров
systemctl list-timers logrotate.timer
Стандартный таймер настроен в конфигурационном файле /usr/lib/systemd/system/logrotate.timer (в современных дистрибутивах путь /lib/ является симлинком на /usr/lib/). Он запускается ежедневно (OnCalendar=daily) с окном точности в 1 час (AccuracySec=1h). Это позволяет ядру объединять системные пробуждения и снижать дисковую нагрузку в дата-центрах:
[Unit]
Description=Daily rotation of log files
Documentation=man:logrotate(8) man:logrotate.conf(5)
[Timer]
OnCalendar=daily
AccuracySec=1h
Persistent=true
[Install]
WantedBy=timers.target
Основной файл конфигурации расположен по пути /etc/logrotate.conf. Он задает глобальные параметры по умолчанию для всей операционной системы (ротация syslog, auth.log, wtmp). Все индивидуальные настройки сервисов (Nginx, Apache, PHP-FPM, MySQL) помещаются в отдельный каталог /etc/logrotate.d/ в виде самостоятельных конфигурационных файлов.
История и временные метки последней успешной ротации каждого отдельного файла журнала фиксируются в системном файле статуса /var/lib/logrotate/status.
3. Настройка ротации логов Nginx: сигналы USR1, директивы и сжатие
Веб-сервер Nginx пишет журналы доступа и ошибок в каталог /var/log/nginx/. Стандартный пакет Nginx в Debian и Ubuntu из коробки поставляет конфигурационный файл /etc/logrotate.d/nginx со схемой daily, rotate 14, compress и delaycompress. Для небольших сайтов этого достаточно, однако на нагруженных веб-серверах и API стоковый конфиг требует оптимизации: добавления размерных ограничений (maxsize), самодокументируемого именования с датами (dateext) и разделения прав (su).
Ниже представлен оптимизированный production-конфиг, адаптированный под высокую нагрузку и экспорт в долгосрочные хранилища:
# Оптимизированная конфигурация ротации Nginx: /etc/logrotate.d/nginx
/var/log/nginx/*.log {
daily
missingok
rotate 14
maxsize 100M
compress
delaycompress
dateext
dateformat -%Y%m%d-%H%M%S
su www-data adm
notifempty
create 0640 www-data adm
sharedscripts
prerotate
if [ -d /etc/logrotate.d/httpd-prerotate ]; then run-parts /etc/logrotate.d/httpd-prerotate; fi endscript
postrotate
invoke-rc.d nginx rotate >/dev/null 2>&1 || true
endscript
}
Если вы используете ручную сборку Nginx или хотите гарантировать прямую отправку сигнала через PID-файл, блок postrotate можно оформить в каноническом виде через команду kill:
postrotate
[ -s /run/nginx.pid ] && kill -USR1 $(cat /run/nginx.pid)
endscript
Разберем значение каждой ключевой директивы:
daily: запускать процедуру ротации каждый день. Допустимы также значенияweekly,monthlyили привязка к размеру (size).missingok: не генерировать системную ошибку, если какой-то лог-файл (например, от удаленного виртуального хоста) временно отсутствует.rotate 14: хранить ровно 14 архивных копий логов. На 15-й день самый старый архив автоматически удаляется с диска.compress: сжимать архивные копии утилитой gzip. Текстовые логи сжимаются на 85–92%, превращая 1 ГБ лога в ~80–120 МБ архива.delaycompress: откладывает сжатие только что переименованного файлаaccess.log.1до следующего цикла ротации (в текущем цикле сжимается толькоaccess.log.2). Это дает двойное преимущество: во-первых, вчерашний журнал остается в несжатом виде для быстрого анализа утилитамиtail,grep,awkили парсерами трафика без распаковки; во-вторых, для внешних сборщиков логов (Promtail, Vector, Filebeat) исключается гонка, когда файл архивируется во время вычитки последних строк.dateextиdateformat: добавляет дату к имени архива. Критический нюанс: стандартный макрос даты-%Y%m%dимеет точность до дня. Если на сервере настроен почасовой запуск с порогомmaxsize, повторная ротация за те же сутки будет молча пропущена из-за конфликта имен (архив с сегодняшней датой уже существует). Чтобы многократная ротация в течение суток работала корректно, обязательно добавляйте суффикс времениdateformat -%Y%m%d-%H%M%S(поддерживается в Logrotate ≥ 3.9.2) либо используйте классическую числовую нумерацию.su www-data adm: директива безопасного переключения пользователя и группы. Критически важна, если каталог логов принадлежит не root, а сервисному аккаунту: предотвращает сбой Logrotate с ошибкой «error: skipping because parent directory has insecure permissions».olddir archives/: (опционально) перемещает созданные сжатые архивы в отдельный подкаталог, сохраняя рабочую папку логов чистой.kill -USR1: передача POSIX-сигнала USR1 главному процессу Nginx. Получив данный сигнал, Nginx не перезагружает конфигурацию и не сбрасывает клиентские соединения, а переоткрывает файлы логов по их путям (pathname), связываясь со свежесозданным файлом (с новым inode), в то время как дескриптор старого ротированного файла корректно закрывается.
Если вам требуется готовый скрипт с генерацией конфигураций под PHP-FPM и кастомные пулы пользователей, воспользуйтесь нашим генератором конфигураций Logrotate для Nginx и PHP-FPM.
4. Ротация логов приложений без поддержки сигналов: режим copytruncate
Далеко не все сервисы и фреймворки умеют переоткрывать файлы логов по сигналу SIGUSR1 или SIGHUP. Микросервисы на Node.js, Python (FastAPI, Flask), Go или Java-приложения, пишущие вывод в текстовый файл через операторы перенаправления (>> /var/log/app.log), при переименовании файла утилитой Logrotate продолжат писать в переименованный архив app.log.1 до полной остановки процесса.
Для таких приложений в Logrotate предусмотрен специальный режим — copytruncate.
# Конфигурация для Node.js / Python сервиса: /etc/logrotate.d/my-backend
/var/log/my-backend/*.log {
daily
rotate 7
maxsize 100M
copytruncate
compress
delaycompress
missingok
notifempty
}
При включенной директиве copytruncate Logrotate не переименовывает оригинальный файл. Вместо этого он:
- Копирует текущее содержимое
app.logв файл архиваapp.log.1. - Обнуляет размер исходного файла на месте (эквивалентно системному вызову
truncate -s 0).
Плюс: процесс приложения не замечает ротации, файловый дескриптор не меняется, перезагрузка демона не требуется.
Минус: существует микросекундный зазор между завершением копирования и обнулением файла. Строки лога, поступившие ровно в этот момент, могут быть потеряны. Для финансового аудита это критично, но для 99% веб-сервисов и логов приложений режим copytruncate является идеальным решением.
Обратите внимание на директиву maxsize 100M (поддерживается начиная с Logrotate ≥ 3.8.1 и гарантированно доступна во всех актуальных версиях Ubuntu и Debian). В отличие от size, директива maxsize запускает ротацию лога при достижении 100 МБ даже до наступления планового интервала (например, в середине суток).
Logrotate — это не фоновый демон непрерывного слежения, а утилита пакетного запуска. Она проверяет размер файлов только в момент своего выполнения. Если в системе активен только стандартный суточный таймер systemd (срабатывающий раз в полночь), то при аномальном цикле ошибок или DDoS-атаке лог может вырасти до нескольких гигабайт за день, и директива maxsize сработает только ночью. Чтобы размерные пороги действительно защищали диск под нагрузкой, настройте почасовой вызов утилиты: добавьте задачу в /etc/cron.hourly/logrotate или создайте отдельный почасовой таймер systemd.
Если вы настраиваете кастомный запуск ротации по cron-расписанию, сгенерировать точный синтаксис поможет наш генератор расписаний Crontab.
5. Нагрузка на I/O и выбор дисковой подсистемы VDS для интенсивного логирования
Процедура сжатия сотен мегабайт текстовых логов утилитой gzip и одновременная непрерывная запись потока access.log создают существенную нагрузку на дисковую подсистему (Disk I/O). На виртуальных машинах с медленными сетевыми блочными хранилищами в момент выполнения компрессии резко подскакивает метрика %iowait: процессор блокируется в ожидании завершения дисковых операций ввода-вывода, что приводит к микрофризам веб-сервера и скачкообразному росту TTFB (Time to First Byte) для реальных пользователей.
Чтобы фоновая архивация не влияла на отзывчивость сайтов и API, критически важно выбирать хостинг-провайдеров с локальными NVMe-накопителями энтерпрайз-класса, высоким лимитом IOPS на запись и гарантированной пропускной способностью шины. Проверить сетевые маршруты и хостинг текущего сервера можно через чекер хостинга и провайдера SysKit.
Ниже представлены надежные облачные площадки с проверенными высокоскоростными NVMe-дисками, оптимальные для production-стеков с высокой интенсивностью дисковой записи:
Рекомендуемые VDS-провайдеры
Отказоустойчивые сервера с быстрыми NVMe и каналом до 1 Гбит/с
Timeweb Cloud — Быстрые NVMe VDS
Высокочастотные процессоры 3.3+ ГГц для быстрой фоновой компрессии логов gzip, скоростные NVMe-диски и безлимитный трафик 1 Гбит/с.
Selectel — Надежные облачные серверы
Отказоустойчивые серверы в сертифицированных дата-центрах Tier III с гарантированными ресурсами и стабильным дисковым вводом-выводом для баз данных.
Beget — Простота для веб-разработчиков
Удобная панель управления сервером, быстрое развертывание стеков LAMP/LEMP в один клик, автоматические резервные копии и круглосуточная поддержка.
6. Укрощение логов Docker: daemon.json и Docker Compose
Главная «черная дыра» свободного места на современных VDS — контейнеры Docker. По умолчанию движок Docker использует стандартный драйвер логирования json-file, который перехватывает вывод stdout и stderr контейнеров и записывает его в текстовый файл по пути:
/var/lib/docker/containers/<container-id>/<container-id>-json.log
Самое опасное заключается в том, что по умолчанию Docker не имеет ограничений на максимальный размер лог-файла! Если контейнер (например, PostgreSQL, Node.js или Nginx) начинает спамить ошибками или сообщениями отладки, этот единственный файл может разрастись до 40–80 ГБ, полностью обрушив файловую систему хоста.
Глобальное ограничение логов в daemon.json
Чтобы ограничить аппетиты всех будущих контейнеров на уровне самого демона Docker, создайте или отредактируйте конфигурационный файл /etc/docker/daemon.json:
{
"log-driver": "json-file",
"log-opts": {
"max-size": "50m",
"max-file": "3"
}
}
После сохранения файла перезапустите службу Docker:
sudo systemctl restart docker
Изменения в daemon.json применяются только к новым контейнерам. Контейнеры, которые были запущены до правки конфига, продолжат работать со старыми безлимитными настройками! Чтобы новые лимиты вступили в силу для проектов Docker Compose, их необходимо принудительно пересоздать командами docker compose down && docker compose up -d.
Индивидуальные лимиты в docker-compose.yml
Если вы хотите настроить индивидуальные лимиты для конкретного сервиса (например, выделить больше объема на логи веб-сервера и меньше для фоновых воркеров), укажите блок logging прямо в манифесте docker-compose.yml:
services:
web:
image: nginx:alpine
restart: unless-stopped
logging:
driver: "json-file"
options:
max-size: "20m"
max-file: "3"
# Лимиты ресурсов cgroups для защиты памяти сервера от OOM Killer
mem_limit: 256m
cpus: 1.0
Примечание по синтаксису Compose: директивы mem_limit и cpus на уровне сервиса являются надежным способом ограничения ресурсов в Docker Compose v2 на standalone-серверах (в отличие от секции deploy.resources.limits, которая исторически предназначалась для Swarm и в ранних версиях standalone Compose могла игнорироваться без специального флага совместимости).
Сгенерировать готовый production-манифест с настроенными параметрами логирования и ограничениями ресурсов поможет наш онлайн генератор Docker Compose стеков. Для удобного реалтайм-просмотра логов контейнеров в веб-интерфейсе без тяжелого стека ELK рекомендуем ознакомиться со статьей о легковесном мониторинге логов Docker в Dozzle.
7. Ограничение дискового пространства systemd-journald: journald.conf
Системный журнал systemd-journald собирает логи ядра, служб systemd и системных событий в бинарном формате в каталоге /var/log/journal/. По умолчанию journald может резервировать до 10% от общего объема корневого раздела диска (на диске 40 ГБ это до 4 ГБ свободного пространства).
Проверить текущий объем, занятый системным журналом, можно командой:
# Текущий размер бинарных логов journald на диске
journalctl --disk-usage
# Пример вывода:
# Archived and active journals take up 2.4G in the file system.
Настройка лимитов в journald.conf
Чтобы ограничить максимальный размер журнала и срок хранения архивных записей, отредактируйте конфигурационный файл /etc/systemd/journald.conf:
[Journal]
# Максимальный общий объем диска под системный журнал
SystemMaxUse=500M
# Минимальный гарантированный объем свободного места на диске
SystemKeepFree=1G
# Максимальный размер одного файла журнала до ротации
SystemMaxFileSize=50M
# Максимальный срок хранения архивных логов
MaxRetentionSec=1month
Примените новую конфигурацию перезапуском демона journald (операция не прерывает работу других системных служб):
sudo systemctl restart systemd-journald
Мягкая ротация и экстренная очистка переполненного журнала
Если диск переполнен и требуется немедленно освободить пространство, перед удалением архивных данных зафиксируйте активный бинарный журнал командой journalctl --rotate. Это мягко переведет текущий файл в категорию архивных без перезапуска демона и прерывания сбора системных событий, после чего вакуумизация выполнится максимально чисто:
# 1. Мягкая ротация активного журнала без прерывания службы
sudo journalctl --rotate
# 2. Вакуумизация: оставить не более 200 МБ самых свежих записей
sudo journalctl --vacuum-size=200M
# Либо удалить все архивные записи старше 7 дней
sudo journalctl --vacuum-time=7d
Для построения централизованного сбора и анализа логов с нескольких серверов изучите наше руководство по настройке стека Grafana Loki и Promtail на VDS.
8. Тестирование, dry-run отладка и алерты при переполнении диска
Ни в коем случае не ждите наступления полуночи, чтобы проверить, сработает ли созданная конфигурация Logrotate. Любая синтаксическая ошибка в конфигурационном файле может привести к тому, что ротация тихо пропустит проблемный сервис, и диск продолжит заполняться.
Режим имитации (Dry-Run Debugging)
Ключ -d (debug) запускает Logrotate в режиме глубокой проверки синтаксиса и логики. Утилита выводит подробный пошаговый отчет о найденных файлах, текущих датах, критериях срабатывания и планируемых командах, не внося никаких изменений в файлы и не отправляя сигналы процессам:
# Тестирование конкретного конфигурационного файла Nginx
sudo logrotate -d /etc/logrotate.d/nginx
# Тестирование всей общесистемной конфигурации
sudo logrotate -d /etc/logrotate.conf
Обратите внимание на строки вывода: Logrotate явно напишет rotating pattern: /var/log/nginx/*.log after 1 days и укажет, требуется ли ротация в текущий момент или файл еще не достиг заданных критериев.
Принудительное выполнение ротации (Force Run)
Если вы хотите немедленно протестировать ротацию в боевом режиме и убедиться, что файлы корректно архивируются, а процессы не теряют дескрипторы, выполните запуск с ключами -v (verbose) и -f (force):
# Принудительная ротация общесистемной конфигурации
sudo logrotate -vf /etc/logrotate.conf
# Либо принудительная ротация конкретного конфига с изолированным state-файлом:
sudo logrotate -vf -s /tmp/logrotate-test.status /etc/logrotate.d/nginx
# Проверка результата в каталоге логов
ls -la /var/log/nginx/
При запуске logrotate -vf /etc/logrotate.d/nginx без ключа -s утилита перезапишет дату последней ротации в главном файле /var/lib/logrotate/status. Использование флага -s /tmp/logrotate-test.status изолирует тестовый запуск и гарантирует, что штатный суточный таймер systemd не пропустит следующую ротацию остальных служб.
После выполнения команды в каталоге /var/log/nginx/ должен появиться созданный архив и свежий пустой файл access.log с правами 0640.
Скрипт мониторинга свободного места с алертом
Чтобы не пропустить аварийное переполнение диска сторонними временными файлами или дампами, добавьте простой bash-скрипт в cron, который будет предупреждать администратора при заполнении диска более 85%:
#!/usr/bin/env bash
# /usr/local/bin/check_disk.sh
THRESHOLD=85
USAGE=$(df --output=pcent / | tail -1 | tr -dc '0-9')
if [ -n "$USAGE" ] && [ "$USAGE" -gt "$THRESHOLD" ]; then
echo "ВНИМАНИЕ: Корневой раздел диска VDS заполнен на $USAGE%!"
# Здесь можно отправить алерт в Telegram через бота или системный mail
fi
9. Чек-лист сисадмина: защита диска VDS от аварийного переполнения
Чтобы ваш сервер годами работал без сбоев из-за нехватки дискового пространства, сверьте конфигурацию с контрольным чек-листом:
-
✓
Служба Logrotate активна: таймер
logrotate.timerзапущен в systemd и выполняет суточное расписание. - ✓ Включено сжатие delaycompress: логи Nginx сжимаются утилитой gzip с отложенным циклом, исключая повреждение активных дескрипторов.
- ✓ Применен copytruncate для кастомных сервисов: Node.js и Python микросервисы ротируются без обрыва фоновых процессов и падения соединения.
-
✓
Ограничен Docker daemon: в
/etc/docker/daemon.jsonпрописаны лимитыmax-size: 50mиmax-file: 3, а контейнеры пересозданы. -
✓
Настроен systemd-journald: параметр
SystemMaxUse=500Mгарантирует, что бинарный журнал не займет более 500 МБ. -
✓
Запрещено ручное удаление rm: при необходимости экстренной очистки логов файлы обнуляются командой
truncate -s 0или через vacuuming journald. -
✓
Конфигурации проверены в dry-run: команда
logrotate -dне выдает синтаксических предупреждений.
Сгенерируйте готовый конфиг Logrotate за 1 минуту
Используйте интерактивный онлайн-генератор SysKit для создания проверенных конфигурационных файлов Logrotate под ваши сервисы и расписания.
Открыть генератор конфигурации Logrotate