MHOSTMHOST

Автобэкапы по расписанию (cron)

Ручной запуск бэкапа рано или поздно забывается — а cron умеет делать это без вас: сам запускает скрипт rsync или restic по расписанию, каждую ночь, без вашего участия. В этой статье — как поставить такой скрипт на расписание через crontab, не потерять его логи и не запутаться в часовых поясах.

Что понадобится

  • Linux VPS mHost — подходит любой из дистрибутивов: Ubuntu 22.04/24.04, Debian 11/12, AlmaLinux/Rocky Linux 8/9.
  • Доступ по SSH под root или пользователем с sudo.
  • Рабочий скрипт или команда бэкапа на основе rsync или restic, которую вы уже проверили вручную. Если такого скрипта ещё нет — сначала настройте сам инструмент по статьям «Бэкапы через rsync» или «Бэкапы через restic»; эта статья — только про расписание и автозапуск, не про сам rsync/restic.
  • Место назначения для бэкапов: отдельный диск/примонтированный том на этом же сервере либо удалённый хост — в зависимости от того, что вы выбрали в статьях выше.

Шаг 1. Подготовьте скрипт бэкапа

cron умеет запускать что угодно — команду, скрипт, целую программу, — но длинную команду удобнее не писать прямо в crontab, а оформить отдельным исполняемым файлом. Так его проще редактировать, тестировать вручную и переиспользовать.

Ниже — два минимальных примера. Возьмите вариант, который соответствует вашему инструменту (подробности — по ссылкам из раздела выше), и сохраните как /usr/local/bin/backup.sh.

Скрипт на основе rsync

bash
sudo nano /usr/local/bin/backup.sh
bash
#!/bin/sh
set -e

SRC="/var/www"
DEST="/mnt/backup/www"

rsync -a --delete "$SRC" "$DEST"

-a (--archive) — рекурсивно скопировать каталог, сохранив права, время изменения, символьные ссылки и владельца; --delete — удалить в DEST файлы, которых уже нет в SRC, чтобы копия оставалась зеркалом, а не только росла. DEST может быть и удалённым хостом по SSH (user@host:/path, с флагом -e ssh) — про это и про остальные флаги rsync см. статью «Бэкапы через rsync».

Скрипт на основе restic

Репозиторий restic создаётся один раз, вручную, заранее. Сначала заведите файл с паролем репозитория:

bash
sudo mkdir -p /etc/restic
sudo nano /etc/restic/password
sudo chmod 600 /etc/restic/password

Впишите в файл /etc/restic/password один длинный пароль (без переноса строки в конце) и сохраните — это пароль шифрования репозитория, а не пароль пользователя ОС. Затем инициализируйте репозиторий:

bash
sudo restic --repo /mnt/backup/restic-repo --password-file /etc/restic/password init

Тот же путь к репозиторию и тот же файл пароля идут первыми строками и в самом скрипте — уже как переменные окружения RESTIC_REPOSITORY и RESTIC_PASSWORD_FILE. Тогда restic не будет спрашивать их интерактивно при запуске из cron, где спросить их попросту не у кого:

bash
sudo nano /usr/local/bin/backup.sh
bash
#!/bin/sh
set -e

export RESTIC_REPOSITORY="/mnt/backup/restic-repo"
export RESTIC_PASSWORD_FILE="/etc/restic/password"

restic backup /var/www
restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune

restic backup создаёт новый снимок; restic forget с флагами --keep-daily/--keep-weekly/--keep-monthly удаляет старые снимки по политике хранения, а --prune сразу освобождает место, занятое уже неиспользуемыми данными. /mnt/backup/restic-repo может быть не только локальным путём, но и SFTP- или S3-совместимым хранилищем — про выбор backend'а, восстановление из копии и тонкую настройку retention см. статью «Бэкапы через restic».

Совет: каталог назначения (/mnt/backup/...) должен существовать заранее — это может быть отдельный примонтированный диск на этом же VPS либо смонтированный удалённый ресурс. Если бэкап лежит на том же физическом диске, что и сами данные, вы не защищены от аварии этого диска или узла — для надёжного бэкапа копия должна храниться отдельно от сервера.

Сделайте скрипт исполняемым и один раз запустите его руками — прежде чем доверять его cron, убедитесь, что он вообще отрабатывает:

bash
sudo chmod +x /usr/local/bin/backup.sh
sudo /usr/local/bin/backup.sh
Готово: если скрипт отработал без ошибок и в DEST/репозитории появились свежие данные — можно переходить к расписанию.

Шаг 2. Откройте crontab

У каждого пользователя свой отдельный crontab. Команда crontab -e открывает crontab текущего пользователя; чтобы отредактировать crontab root (обычно то, что нужно для системного бэкап-скрипта вроде /var/www), выполните:

bash
sudo crontab -e
Совет: при самом первом запуске crontab -e на Ubuntu/Debian система предложит выбрать редактор (обычно в списке — nano, vim.basic, vim.tiny, ed); это делает утилита select-editor, а выбор запоминается на будущее в ~/.selected-editor. На AlmaLinux/Rocky Linux (пакет cronie) такого диалога нет — crontab сразу открывается в редакторе из переменной $VISUAL/$EDITOR, а если они не заданы — в vi. Сменить редактор позже можно командой select-editor (Ubuntu/Debian) или экспортом EDITOR=nano перед запуском crontab -e.

Полезные соседние команды:

  • crontab -l — показать текущий crontab, ничего не редактируя.
  • crontab -r — удалить весь crontab пользователя целиком, без подтверждения.
Важно: crontab -r стирает все задачи пользователя разом, а не одну строку. Чтобы убрать только одну задачу, откройте crontab через -e и удалите нужную строку вручную.

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

bash
# Ubuntu / Debian
systemctl status cron

# AlmaLinux / Rocky Linux
systemctl status crond

Если служба не найдена — установите пакет и включите автозапуск:

bash
# Ubuntu / Debian
sudo apt install -y cron
sudo systemctl enable --now cron

# AlmaLinux / Rocky Linux
sudo dnf install -y cronie
sudo systemctl enable --now crond
Проверьте: на большинстве образов mHost cron уже установлен и запущен по умолчанию — команды выше пригодятся, только если systemctl status показывает, что служба не найдена.

Шаг 3. Синтаксис расписания cron

Каждая строка crontab описывает расписание пятью полями, а дальше идёт сама команда. Порядок полей: минута · час · день месяца · месяц · день недели.

  • минута — 0–59
  • час — 0–23
  • день месяца — 1–31
  • месяц — 1–12
  • день недели — 0–7 (0 и 7 — воскресенье)
  • * — «любое значение», всё поле целиком.
  • , — список значений: 1,15 — 1-го и 15-го числа.
  • - — диапазон: 8-11 в поле часа — с 8 до 11 включительно.
  • / — шаг: */2 в поле часа — каждый второй час (0, 2, 4, …).

Примеры:

  • 0 3 * * * — Каждую ночь в 03:00
  • 30 2 * * 0 — Каждое воскресенье в 02:30
  • 0 */6 * * * — Каждые 6 часов (00:00, 06:00, 12:00, 18:00)
  • 0 3 1 * * — 1-го числа каждого месяца в 03:00

Вместо пяти полей можно использовать короткие «имена»: @daily (эквивалент 0 0 * * *), @weekly (0 0 * * 0), @monthly, @hourly, @reboot (один раз при старте системы) и другие. Для ночного бэкапа удобнее явные пять полей — так вы точно контролируете час: @daily сработает в полночь, а не, скажем, в 03:00, когда нагрузка на диск обычно ниже.

Шаг 4. Добавьте ночную задачу и перенаправьте логи

Откройте crontab root и добавьте строку:

bash
sudo crontab -e
0 3 * * * /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1
  • 0 3 * * * — каждую ночь в 03:00 (о том, по какому часовому поясу, — в шаге 5 ниже).
  • >> /var/log/backup.log — дописывать стандартный вывод скрипта в конец файла (> вместо >> каждый раз перезаписывал бы лог с нуля).
  • 2>&1 — направить туда же и поток ошибок (stderr); порядок важен: сначала >> перенаправляет stdout в файл, и только потом 2>&1 говорит «поток 2 — туда же, куда сейчас смотрит поток 1».
Совет: по умолчанию, если вывод не перенаправить, cron пытается отправить его почтой владельцу crontab (переменная MAILTO) — а на свежем VPS локальный почтовый сервер (MTA) обычно не настроен, и cron просто отбрасывает вывод, оставив об этом запись только в собственном логе (CRON[…]: (CRON) info (No MTA installed, discarding output)), которую почти никто не читает. Перенаправление в файл — самый надёжный способ не потерять вывод скрипта независимо от того, настроена почта на сервере или нет.
Совет: если когда-нибудь бэкап займёт больше времени, чем интервал между запусками, два экземпляра скрипта могут пересечься. Чтобы исключить это, оберните вызов в flock:
0 3 * * * /usr/bin/flock -n /var/run/backup.lock /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1

Флаг -n — не ждать, а сразу выйти, если предыдущий запуск ещё не закончился.

Файл /var/log/backup.log будет расти бесконечно, если его не ротировать, — при необходимости заведите для него отдельное правило logrotate (отдельная тема, здесь не рассматривается).

Сохраните файл и проверьте, что задача добавилась:

bash
sudo crontab -l

Шаг 5. Часовой пояс

cron ориентируется на системные дату и время сервера — а не на часовой пояс, который вы держали в уме, когда писали 0 3 * * *. Проверьте текущий часовой пояс сервера:

bash
timedatectl

При необходимости смените его (например, на московское время):

bash
sudo timedatectl set-timezone Europe/Moscow

Список всех доступных зон — timedatectl list-timezones.

Важно: переопределить часовой пояс отдельно для одного crontab, не трогая системный, можно по-разному в зависимости от дистрибутива: - На AlmaLinux/Rocky Linux (cronie) достаточно добавить строку CRON_TZ=Europe/Moscow в начало crontab — часовой пояс изменится для всех задач ниже этой строки. - На Ubuntu/Debian (пакет cron) переменная CRON_TZ не поддерживается: cron всегда планирует запуск по системному времени сервера, а TZ, даже если задать её в crontab, повлияет только на то, что видит сам запущенный скрипт (например, вывод команды date внутри него), но не на момент срабатывания задачи. Единственный способ, который работает одинаково на всех перечисленных дистрибутивах, — выставить нужный часовой пояс на уровне всей системы через timedatectl, как показано выше, и писать расписание уже исходя из него.
Совет: если системный часовой пояс использует переход на летнее/зимнее время, в момент самого перевода часов задача теоретически может не сработать (при переводе стрелок вперёд один час «пропадает») или сработать дважды (при переводе назад). Для инфраструктурных задач вроде бэкапа многие держат сервер в Etc/UTC, где переводов времени не бывает в принципе, — так расписание остаётся предсказуемым круглый год.

Шаг 6. Проверьте, что бэкап действительно отработал

Дайте задаче сработать хотя бы раз (не обязательно ждать ночи — можно временно поставить время на пару минут вперёд, проверить результат, а затем вернуть 0 3 * * * обратно) и посмотрите лог скрипта:

bash
tail -n 50 /var/log/backup.log

Заодно проверьте, что сам cron видел и запускал задачу, — это отдельная запись, не совпадающая с выводом вашего скрипта:

bash
# Ubuntu / Debian
journalctl -u cron --since today
grep CRON /var/log/syslog

# AlmaLinux / Rocky Linux
journalctl -u crond --since today
sudo tail -n 50 /var/log/cron

Наконец, убедитесь, что в месте назначения действительно появились свежие данные — новый файл/каталог с сегодняшней датой для rsync или новый снимок для restic (restic snapshots).

Готово: лог без ошибок и свежие данные в DEST/репозитории — расписание настроено и работает без вашего участия.

Что дальше

Детали самого инструмента — установку, выбор места хранения, восстановление из копии, тонкую настройку retention — смотрите в статьях «Бэкапы через rsync» и «Бэкапы через restic».