Шаг 14 · Автоматизация · 25 минут

Cron и автоматизация задач на VPS

Резервная копия «когда вспомню», проверка сертификата «как-нибудь потом» — ручные задачи рано или поздно забываются. Cron делает их регулярными и незаметными.

Как устроен cron

На сервере работает демон cron — он просыпается раз в минуту, читает список задач и запускает те, для которых наступило время. Задачи хранятся в двух местах: персональный crontab каждого пользователя (свой у root, свой у обычного пользователя) и системный /etc/crontab вместе с файлами в /etc/cron.d/. Ничего сложнее не происходит — cron не «умный планировщик» с очередями и приоритетами, а простой цикл: минута прошла, сверил расписание, запустил команду.

Демон уже установлен и включён на большинстве Ubuntu-образов. Проверить это можно так:

systemctl status cron

Редактируем crontab

Для задач текущего пользователя используется команда:

crontab -e

При первом запуске она попросит выбрать редактор (обычно удобно оставить nano). Файл открывается пустым или с комментариями-подсказками — расписания добавляются построчно.

Важно понимать: crontab пользователя root и crontab обычного пользователя — это два разных файла. Если выполнить crontab -e от имени своего пользователя, задача будет запускаться с его правами и окружением. Для системных задач — бэкап всей директории /var/www, обновление пакетов, работа с логами демонов — обычно используют root, потому что только у него хватает прав на нужные файлы:

sudo crontab -e

Посмотреть, что уже стоит в расписании, можно без редактирования:

crontab -l
sudo crontab -l

Синтаксис расписания

Каждая строка crontab — это пять полей времени и команда:

*    *    *    *    *    команда
│    │    │    │    │
│    │    │    │    └─ день недели (0-6, 0 = воскресенье)
│    │    │    └─── месяц (1-12)
│    │    └────── день месяца (1-31)
│    └───────── час (0-23)
└──────────── минута (0-59)

Звёздочка означает «любое значение». Несколько практических примеров, которые покрывают большинство задач:

0 3 * * * /usr/local/bin/backup.sh
  # каждый день в 3:00 ночи

*/15 * * * * /usr/local/bin/healthcheck.sh
  # каждые 15 минут

0 4 * * 0 /usr/local/bin/weekly-cleanup.sh
  # каждое воскресенье в 4:00

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

Логирование заданий

Частая ошибка новичков — команда прекрасно работает в терминале, но «ничего не происходит» по расписанию. Причина в том, что cron запускает задачи в минимальном окружении: без переменных из .bashrc, часто с урезанным PATH, не в домашней директории. То, что интерактивная оболочка находит автоматически, cron может просто не увидеть.

Поэтому вывод любой задачи стоит перенаправлять в файл — иначе результат работы (включая ошибки) уходит в почту root или пропадает молча:

0 3 * * * /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1

>> дописывает вывод в конец файла, а 2>&1 перенаправляет туда же поток ошибок — без этой части скрипт может годами падать с ошибкой, а лог будет выглядеть пустым.

Прежде чем добавлять скрипт в crontab, запустите его вручную от того же пользователя и с тем же путём, что будет использовать cron. И всегда добавляйте перенаправление вывода в лог — молча падающее задание хуже, чем отсутствие автоматизации: оно создаёт иллюзию, что бэкапы делаются, хотя на самом деле нет.

Типовые сценарии

Несколько заданий, которые почти всегда есть смысл поставить на VPS:

0 2 * * * /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1
  # ежедневный бэкап (см. отдельный гайд про резервные копии)

0 5 * * 1 docker system prune -f >> /var/log/docker-cleanup.log 2>&1
  # раз в неделю чистим неиспользуемые образы и слои Docker

30 3 * * * certbot renew --quiet >> /var/log/certbot-renew.log 2>&1
  # обновление сертификата — но сначала проверьте, не установлен ли свой таймер

Про certbot renew стоит сказать отдельно: пакет certbot чаще всего сам ставит себе systemd-таймер или задачу в /etc/cron.d/ при установке. Дублировать её вручную не нужно — проверьте, нет ли она уже там, прежде чем добавлять свою:

systemctl list-timers | grep certbot
ls /etc/cron.d/ | grep certbot

Для ротации логов приложения обычно достаточно системного logrotate (он и сам запускается через cron/systemd), но если приложение пишет логи в нестандартном формате, простой скрипт архивации и удаления старых файлов раз в сутки решает задачу без лишних зависимостей.

systemd timers как альтернатива

Для более сложных случаев — когда одна задача должна запускаться только после успешного завершения другой, или когда нужно логирование из коробки через journalctl — существуют systemd timers. Они гибче cron, но и настраиваются через пару дополнительных unit-файлов (.service + .timer). Для подавляющего большинства задач на обычном VPS — бэкапы, очистка, проверки — простого cron вполне достаточно, и нет смысла усложнять там, где не требуется.

Проверяем, что задача реально выполняется

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

grep CRON /var/log/syslog
  # на системах без /var/log/syslog:
journalctl -u cron --since "1 hour ago"

Там будет видно, что задача была запущена в ожидаемое время и с каким кодом завершения. Это отдельная проверка от самого лог-файла задачи — syslog подтверждает факт запуска, а /var/log/backup.log и подобные файлы показывают, что именно произошло внутри скрипта.

Типовые ошибки

  • Относительные пути в скрипте — cron запускает задачи не из домашней директории, поэтому cd в начале скрипта или абсолютные пути обязательны.
  • Команда работает в терминале, но не в cron — почти всегда дело в другом PATH: в crontab он короче, и утилиты вроде docker или certbot могут быть не найдены по имени без полного пути.
  • Забытый 2>&1 — ошибки скрипта уходят в никуда, и задание месяцами «работает», хотя на самом деле падает на первой же строке.
  • Часовой пояс сервера отличается от ожидаемого — расписание «в 3 ночи» может оказаться днём, если сервер живёт в UTC; проверить и при необходимости поменять можно через timedatectl.
  • Слишком частые тяжёлые задачи — бэкап или docker prune каждые 5 минут создают лишнюю нагрузку; для большинства сценариев хватает суточного или недельного расписания.

Что дальше

Cron закрывает регулярное обслуживание сервера, но сам процесс выкладки новых версий кода можно автоматизировать отдельно: автоматизируем сам процесс деплоя через GitHub Actions →