Cron vs systemd-таймеры: что выбрать для задач
Cron vs systemd-таймеры: синтаксис, логирование, догоняющие запуски и зависимости — плюс таблица решений, чтобы следующая задача попала куда надо.
На современном дистрибутиве используйте systemd-таймер для всего, что вы сами написали и обслуживаете, а cron оставьте для однострочных пользовательских задач и серверов, которые настраивали не вы. Таймеры пишут каждый запуск в журнал, умеют догонять расписания, пропущенные, пока машина была выключена, и зависят от тех же юнит-файлов, что и всё остальное в системе. Cron берёт лаконичностью — одна строка в crontab -e против двух юнит-файлов — и всё ещё остаётся единственным планировщиком, гарантированно существующим в минимальных контейнерах и экзотических Unix-машинах. Ловушка: cron падает молча. Если задача ошибается в 3 часа ночи, cron отправит письмо в локальный ящик, который никто не читает, а таймер даст вам journalctl -u mytimer.service с полным выводом. Ниже — реальные различия, как тестировать каждое, почему таймер может не срабатывать и таблица решений.
В чём разница между cron и systemd-таймером?
Cron — демон, который читает таблицу строк — пять полей времени и команда — и запускает команду, когда настенные часы совпадают. Вся модель. Никакого понятия «задача как объект»: ни юнита, ни статуса, ни зависимостей, ни собственной записи в логе.
systemd-таймер — юнит-файл (foo.timer), который запускает другой юнит-файл (foo.service), когда срабатывает его расписание. Задача — полноценный объект со start, status, logs и отслеживанием сбоев, как у любого сервиса. Расписание либо календарное (как у cron), либо монотонное (OnBootSec=15min, которое не сбить сменой настенных часов).
Практические следствия:
- Логирование: таймеры пишут stdout/stderr в журнал по каждому юниту; cron в лучшем случае шлёт письмо локальному пользователю.
- Пропущенные запуски: таймер с
Persistent=trueвыполнится один раз при загрузке, если расписание пропустили; cron просто пропустит. - Зависимости: таймеры умеют ждать
network-online.targetили точки монтирования; cron заставляет вручную писать retry-логику в скрипте. - Синтаксис: cron — одна строка; таймер — два файла. Это и вся цена перехода.
Какой синтаксис расписания проще — crontab или OnCalendar?
Пять полей cron — компактный действующий чемпион: */15 * * * * — это каждые 15 минут, и большинство админов читает их с закрытыми глазами. OnCalendar= у systemd многословнее, но строго выразительнее, а systemd-analyze calendar покажет ближайшие запуски до того, как вы сохраните расписание, — у cron эквивалентного dry-run нет.
# Cron: каждый день в 03:30
30 3 * * * /usr/local/bin/backup.sh
# systemd: то же расписание, проверяемое до сохранения
systemd-analyze calendar "*-*-* 03:30:00"
# -> Следующий запуск: Пт 2026-09-11 03:30:00 ...
OnCalendar умеет то, что cron чисто выразить не может: Mon..Fri *-*-* 09..17:00:00 (будни, рабочие часы) или *:0/15 с RandomizedDelaySec=10m, чтобы тысяча машин не била по серверу в один и тот же миг.
Как протестировать systemd-таймер, не дожидаясь срабатывания?
Три команды отвечают на всё: посмотреть, что запланировано и когда следующий запуск, выполнить задачу руками ровно так, как её запустил бы таймер, и прочитать её логи:
systemctl list-timers --all # все таймеры, следующий + прошлый запуск
sudo systemctl start backup.service # запустить задачу сейчас, тот же юнит, что у таймера
journalctl -u backup.service -f # смотреть вывод вживую
Обратите внимание на разделение: systemctl start backup.timer взводит расписание; сервис — сама задача. Если таймер виден в list-timers, systemctl status backup.service зелёный, а в журнале есть ваш вывод — цепочка работает целиком.
Как запустить cron-задачу вручную?
Cron-задачи выполняются с урезанным окружением, отчего «в моём шелле работает, в cron падает» — целый жанр багов. Чтобы воспроизвести cron честно, прогоните команду через sh с тем окружением, которое использовал бы сам cron:
crontab -l # сверьте точную строку
env -i SHELL=/bin/sh PATH=/usr/bin:/bin sh -c '/usr/local/bin/backup.sh'
grep CRON /var/log/syslog | tail # cron вообще её запускал? (Debian/Ubuntu)
journalctl -u cron -n 20 # то же самое на systemd-дистрибутивах
Строка с env -i — честный ручной тест: голое окружение с дефолтным PATH от cron. Большинство молчаливых падений cron — голый PATH или неэкранированный % в команде (cron считает % переводом строки), и оба проявляются этим способом немедленно.
Почему мой systemd-таймер не срабатывает?
Четыре причины покрывают почти все случаи — проверяйте в этом порядке:
- Имена сервиса и таймера не совпадают.
foo.timerзапускаетfoo.service— опечатка вOnFailureили переименованный юнит означают, что таймер «срабатывает» в пустоту.systemctl cat foo.timerпоказывает, на что именно он нацелен. - Таймер отредактировали, но не перечитали. После правки юнит-файла:
sudo systemctl daemon-reload && sudo systemctl restart foo.timer. Без этого новое расписание не действует. Persistent=trueбез ожидаемыхOnCalendar-семантик — либо вы смотритеsystemctl status foo.timer(всегда показывает active, даже когда бездействует) вместоlist-timers.- Запускаемый сервис падает мгновенно, и таймер выглядит мёртвым.
journalctl -u foo.service --since -1hпокажет падение, которое скрываетlist-timers.
А когда дойдёте до логов, шпаргалка по journalctl покрывает фильтры (-u, --since, -f), которые делают это быстрым.
Cron vs systemd-таймеры: таблица решений
| cron | systemd-таймер | |
|---|---|---|
| Настройка | одна строка в crontab |
два юнит-файла |
| Логирование | локальная почта, обычно нечитанная | журнал, по каждому юниту |
| Пропущенный запуск (машина выключена) | пропускается | один прогон с Persistent=true |
| Зависимости / порядок | нет — самодел в скрипте | полные зависимости юнитов |
| Случайная задержка | самодел через $RANDOM |
RandomizedDelaySec= |
| Тест/dry-run расписания | нет | systemd-analyze calendar |
| Есть везде | да — контейнеры, BSD, embedded | нужен systemd (PID 1) |
| Задачи пользователя без root | crontab -e |
юниты systemd --user |
Эвристики: завести задачу на своей systemd-машине → таймер. Быстрое личное напоминание или машина, которую вы не собирали → cron. Всё, что с зависимостями, ретраями или потребностью знать, выполнилось ли оно вообще → таймер, всегда. Типовой паттерн — таймер с задачей обслуживания, скажем ночным rsync-бэкапом, где Persistent=true гарантирует, что бэкап случится, даже если машина спала в назначенную минуту, — этого cron просто не умеет.