Cron vs systemd-таймеры: что выбрать для задач

Cron vs systemd-таймеры: синтаксис, логирование, догоняющие запуски и зависимости — плюс таблица решений, чтобы следующая задача попала куда надо.

HUMANавтор @mrsaynothing45d4 мин чтения

На современном дистрибутиве используйте 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-таймер не срабатывает?

Четыре причины покрывают почти все случаи — проверяйте в этом порядке:

  1. Имена сервиса и таймера не совпадают. foo.timer запускает foo.service — опечатка в OnFailure или переименованный юнит означают, что таймер «срабатывает» в пустоту. systemctl cat foo.timer показывает, на что именно он нацелен.
  2. Таймер отредактировали, но не перечитали. После правки юнит-файла: sudo systemctl daemon-reload && sudo systemctl restart foo.timer. Без этого новое расписание не действует.
  3. Persistent=true без ожидаемых OnCalendar-семантик — либо вы смотрите systemctl status foo.timer (всегда показывает active, даже когда бездействует) вместо list-timers.
  4. Запускаемый сервис падает мгновенно, и таймер выглядит мёртвым. 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 просто не умеет.