Скрипт, который работает ночью без вас — и копия, из которой можно восстановиться
cron — служба, которая запускает команды по расписанию. Расписание пользователя редактируется командой crontab -e, просмотр — crontab -l. Строка — пять полей времени и команда:
# мин час день месяц день_недели команда
10 3 * * * /usr/local/bin/backup >> /var/log/backup.log 2>&1
*/15 * * * * /usr/local/bin/check-disk
0 9 * * 1-5 /usr/local/bin/report
30 2 1 * * /usr/local/bin/monthly-cleanup
| Запись | Значение |
|---|---|
* | Любое значение |
*/15 | Каждые 15 (минут, часов…) |
1-5 | Диапазон: дни недели с понедельника по пятницу (0 и 7 — воскресенье) |
9,18 | Список значений |
Системные задания лежат в /etc/crontab и файлах /etc/cron.d/ — у них есть шестое поле: от чьего имени запускать. Скрипты в /etc/cron.daily/ выполняются раз в день целиком (имена файлов без точек — иначе их пропустят).
$ grep -v '^#' /etc/crontab | grep .
SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
17 * * * * root cd / && run-parts --report /etc/cron.hourly
25 6 * * * root test -x /usr/sbin/anacron || { cd / && run-parts --report /etc/cron.daily; }
...
Классическая история: скрипт отлично работает руками, а из cron молча не делает ничего. Причина — другое окружение.
| Отличие | Следствие | Решение |
|---|---|---|
Короткий PATH | Команды из /usr/local/sbin или ~/.local/bin «не найдены» | Полные пути или PATH=… в начале скрипта |
Оболочка /bin/sh | Башизмы в строке crontab не работают | Сложную логику — в скрипт с #!/bin/bash |
| Текущий каталог — домашний | Относительные пути ведут не туда | Абсолютные пути или cd /путь || exit |
| Нет терминала | Вывод теряется или уходит письмом, которое никто не читает | >> журнал 2>&1 или logger |
Знак % | В crontab означает перевод строки: date +%F ломает команду | Экранировать \% или вынести в скрипт |
Нет LANG | Сообщения на английском, кириллица в именах может отображаться неверно | Задать в скрипте, если важно |
Проверка «как в cron» — запуск с пустым окружением: env -i /bin/sh -c '/usr/local/bin/backup'. Если в таком виде скрипт работает, в cron он тоже заработает.
В современных дистрибутивах расписание всё чаще задают таймерами systemd. Таймер состоит из двух файлов в /etc/systemd/system/: служба описывает, что запускать, таймер — когда.
# /etc/systemd/system/backup.service
[Unit]
Description=Резервное копирование сайта
[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup
Nice=10
IOSchedulingClass=idle
# /etc/systemd/system/backup.timer
[Unit]
Description=Резервное копирование сайта ежедневно в 03:10
[Timer]
OnCalendar=*-*-* 03:10:00
Persistent=true
RandomizedDelaySec=5min
[Install]
WantedBy=timers.target
$ sudo systemctl daemon-reload
$ sudo systemctl enable --now backup.timer
$ systemctl list-timers backup.timer
$ sudo systemctl start backup.service # запустить вне расписания, для проверки
$ journalctl -u backup.service -n 20 # весь вывод скрипта — здесь
Формат OnCalendar проверяется командой systemd-analyze calendar — она покажет ближайшие срабатывания:
$ systemd-analyze calendar "Mon..Fri *-*-* 09,18:00" --iterations=3
Original form: Mon..Fri *-*-* 09,18:00
...
Next elapse: Thu 2026-10-15 09:00:00 MSK
Iteration #2: Thu 2026-10-15 18:00:00 MSK
Iteration #3: Fri 2026-10-16 09:00:00 MSK
| cron | Таймер systemd | |
|---|---|---|
| Настройка | Одна строка | Два файла |
| Вывод скрипта | Нужно перенаправлять самому | Автоматически в журнал службы |
| Машина была выключена в момент запуска | Запуск пропущен | Persistent=true — выполнится при включении |
| Запуск, пока идёт предыдущий | Запустится второй экземпляр | Служба уже активна — второй не стартует |
| Ограничение ресурсов, запуск от пользователя | Вручную | Nice=, User=, MemoryMax= в службе |
Для серверов курс рекомендует таймеры: журнал, защита от наложения и пропущенных запусков даются бесплатно. cron остаётся там, где он уже используется, и для быстрых пользовательских задач.
rsync копирует каталоги, передавая только изменения, — локально или через SSH. Ставится пакетом rsync.
$ rsync -av --delete --dry-run site/ mirror/ # сначала посмотреть
sending incremental file list
index.html
deleting css/s.css
css/
sent 183 bytes received 44 bytes 454,00 bytes/sec
total size is 20.015 speedup is 88,17 (DRY RUN)
$ rsync -av --delete site/ mirror/ # потом выполнить
$ rsync -az -e ssh /var/www/site/ backup@srv02:/srv/mirror/site/ # на другой сервер
| Опция | Значение |
|---|---|
-a | Архивный режим: рекурсивно, с правами, владельцами, датами, ссылками |
-v, --stats | Список файлов, итоговая статистика |
-z | Сжимать при передаче по сети |
--delete | Удалять в копии то, чего больше нет в источнике |
-n, --dry-run | Показать, что будет сделано |
--exclude 'cache/' | Не копировать |
rsync -a site/ mirror/ копирует содержимое site, rsync -a site mirror/ — сам каталог, получится mirror/site/. А --delete с перепутанными источником и приёмником стирает оригинал. Поэтому — сначала --dry-run.Зеркало — ещё не резервная копия: случайное удаление файла на сайте с --delete ночью удалит его и в зеркале. Нужны копии на разные даты.
Копия на дату — архив с датой в имени. Сразу после создания его проверяют и считают контрольную сумму, чтобы позже, перед восстановлением, убедиться, что файл не испорчен.
$ name=site-$(date +%F).tar.gz
$ tar -czf "$name" -C /var/www site && gzip -t "$name" && echo "архив цел"
архив цел
$ sha256sum "$name" > "$name.sha256"
$ sha256sum -c "$name.sha256"
site-2026-10-14.tar.gz: ЦЕЛ
$ echo x >> "$name"; sha256sum -c "$name.sha256" # имитация порчи
site-2026-10-14.tar.gz: ПОВРЕЖДЁН
sha256sum: WARNING: 1 computed checksum did NOT match
-C /var/www site — перейти в /var/www и упаковать site: в архиве будут относительные пути site/…, а не var/www/site/…, и распаковать его можно куда угодно. gzip -t проверяет целостность сжатия, tar -tzf — что архив читается.
Без удаления старых копий диск рано или поздно закончится — и тогда не сделается и свежая. Два способа ротации.
По количеству — хранить N последних. ls -1t сортирует по времени, новые сверху; tail -n +8 выдаёт всё начиная с восьмой строки, то есть лишнее при N = 7:
$ ls arch | wc -l
10
$ ls -1t arch/site-*.tar.gz | tail -n +8
arch/site-2026-10-03.tar.gz
arch/site-2026-10-02.tar.gz
arch/site-2026-10-01.tar.gz
$ ls -1t arch/site-*.tar.gz | tail -n +8 | xargs -r rm --
$ ls arch | wc -l
7
Имена архивов здесь без пробелов — вы сами их формируете, поэтому разбор вывода ls допустим. xargs -r не вызывает rm, если удалять нечего.
По возрасту — удалять старше N дней: find /srv/backup -name 'site-*.tar.gz' -mtime +30 -delete. Опасность: если копии перестали создаваться, через месяц find удалит последние. Ротация по количеству такого не допускает — поэтому для резервных копий она надёжнее, а лучше — обе с проверкой, что свежая копия создана перед удалением старых.
Резервная копия, из которой ни разу не восстанавливались, — гипотеза, а не копия. Правило 3-2-1: три копии данных, на двух разных носителях, одна — в другом месте (другой сервер, облачное хранилище, офис).
/etc, ключи и сертификаты. Список — в шапке скрипта.Persistent=true, вывод в journalctl -u; проверка — systemd-analyze calendarrsync -a --delete — зеркало; сначала --dry-run; косая черта в конце источника важнаtar -czf имя-$(date +%F), проверка gzip -t и sha256sum