Короткий ответ

Timer и service — два unit. Зелёный backup.timer не значит, что backup.service отработал успешно. Смотрите systemctl list-timers --all, systemctl cat backup.timer backup.service, journalctl -u backup.service. Persistent=true догоняет пропущенные слоты после выключения; без него ночной reboot «съест» 03:00. OnCalendar= парсится в локали systemd, не в crontab-синтаксисе. Не чините timer правкой cron.

Хост host.example (10.0.20.10), пользователь admin.

Симптомы и как отличить

  • в list-timers колонка LAST пустая;
  • NEXT в прошлом (часы убежали) — NTP;
  • timer triggering, service failed — сервис;
  • ждали cron — cron.
Поле timerСмысл
OnBootSecчерез N после boot
OnUnitActiveSecпериод от прошлого успешного? старта unit — читайте man
OnCalendarкалендарь
Persistentдогон после downtime
RandomizedDelaySecразмазать apt-daily

Возможные причины

  1. systemctl enable сделали только service, не timer.
  2. Имена не парные: backup.timer запускает не backup.service (Unit=).
  3. Persistent=false (дефолт) и машина была выключена.
  4. Невалидный календарь, timer inactive.
  5. ConditionPathExists у service ложен.
  6. Часовой пояс unit vs timedatectl.
  7. OnCalendar с секундами vs AccuracySec=1min — сдвиг.

Диагностика

systemctl list-timers --all --no-pager
systemctl status backup.timer backup.service --no-pager -l
systemctl cat backup.timer
systemctl cat backup.service
systemctl show backup.timer -p TimersCalendar,Persistent,LastTriggerUSec,NextElapseUSecRealtime,Unit

Проверка календаря:

systemd-analyze calendar 'Tue *-*-* 03:15:00'
systemd-analyze calendar '*-*-* 03:15:00' --iterations=5

Журнал:

journalctl -u backup.timer -u backup.service --since '7 days ago' --no-pager

Если LAST есть, а файлов нет — смотрите exit code service, диск, права.

Ручной запуск service, не timer:

sudo systemctl start backup.service
echo $?
systemctl status backup.service --no-pager

Если вручную ок, а по календарю нет — проблема timer/времени, не скрипта.

Время машины:

timedatectl
systemctl show backup.timer -p Timezone

Решение

Сценарий A. Timer не enabled

sudo systemctl enable --now backup.timer
systemctl list-timers backup.timer

enable service не обязателен, если только timer его стартует (RemainAfterExit/Type oneshot типичен).

Сценарий B. Нужен догон после reboot

Drop-in:

[Timer]
Persistent=true
sudo systemctl daemon-reload
sudo systemctl restart backup.timer

Помните: после долгого downtime oneshot может стартовать сразу при boot — нагрузка на диск. Для тяжёлых job добавьте OnBootSec=10min вдобавок или RandomizedDelay.

Сценарий C. Календарь

[Timer]
OnCalendar=*-*-* 03:15:00
AccuracySec=1min

Не копируйте 0 3 * * * внутрь OnCalendar — это не cron.

Сценарий D. Unit= указывает не туда

В [Timer] Unit=backup.service явно. Имя по умолчанию — префикс timer.

Сценарий E. oneshot падает из‑за ENOSPC

Чистите диск, иначе timer будет «срабатывать» в failed. Диск.

Связка timer, service и календарь: частые ошибки enable

systemctl enable backup.service без timer запускает service на boot, если есть WantedBy=multi-user.target, и не создаёт календарь. Наоборот, enable backup.timer достаточно для расписания. Проверьте systemctl list-unit-files 'backup.*'.

OnCalendar=daily это 00:00:00 локального времени systemd. Если timedatectl в UTC, «ночью» для Москвы это не то. Явно пишите *-*-* 03:15:00 и держите TZ хоста осознанно.

RandomizedDelaySec=12h у apt-daily может сдвинуть job на половину суток — это не «timer не работает». Смотрите systemctl list-timers apt-daily.timer колонку NEXT.

Для Type=oneshot без RemainAfterExit=yes unit сразу inactive после успеха. list-timers LAST всё равно обновится. Не ждите is-active backup.service == active как критерий успеха oneshot — смотрите journal и артефакт.

Проверка парсинга с итерациями на неделю:

systemd-analyze calendar 'Mon *-*-* 04:00:00' --iterations=8

Если дата в прошлом относительно Persistent=true, при enable --now job может стартовать немедленно. На тяжёлом dump это сюрприз в рабочее время: сначала enable без --now, потом start в окне.

AccuracySec=1h у некоторых vendor timer (энергосбережение) сдвигает «03:15» куда угодно внутри часа. Для бэкапа с окном 03:00–03:30 поставьте AccuracySec=1min. Проверка: systemctl show backup.timer -p AccuracyUSec.

WakeSystem=true на сервере без RTC wake бесполезен и путает. Не копируйте desktop-unit.

Если service вызывает bash /opt/backup.sh без PATH= в unit, cron-привычка «на сервере же стоит mysql» ломается: в systemd PATH узкий. Дублируйте абсолютные пути как в статье cron, но уже в [Service] Environment=PATH=....

Конфликт имён: пакет установил /lib/systemd/system/backup.timer, вы положили /etc/systemd/system/backup.timer целиком. Полный файл в /etc заменяет vendor, drop-in — нет. systemctl cat покажет, что реально действует. После apt upgrade пакета ваш полный override может скрыть новые After= зависимости — предпочтительнее drop-in.

Логи Started backup.service без Finished при Type=oneshot значат, что скрипт не вышел. Timer не «пропустил», он ждёт или упёрся в TimeoutStartSec (по умолчанию 90 с для многих unit, для oneshot часто infinity — уточните systemctl show -p TimeoutStartUSec). Долгий dump должен иметь явный TimeoutStartSec=6h.

OnCalendar= несколько строк в одном timer — ИЛИ по событиям (сработает на любое совпадение). Не думайте, что это AND. Для «в 3:15 И только в вс» пишите Sun *-*-* 03:15:00.

systemctl reset-failed backup.service перед ручной проверкой, иначе StartLimit не пустит. После серии failed timer может перестать запускать service до reset.

Как проверить, что проблема устранена

Временный календарь через минуту (потом вернуть боевой):

[Timer]
OnCalendar=*-*-* *:0/1:00
Persistent=false

Или:

sudo systemctl start backup.timer
# форсировать:
sudo systemctl start backup.service
journalctl -u backup.service -n 20

Проверьте артефакт job. list-timers LAST обновился.

С admin@10.0.20.10 после суток — LAST не старше ожидаемого интервала.

Если не помогло

  • Failed to parse calendar — systemd-analyze, локаль.
  • Timer в user.slice: systemctl --user, linger loginctl enable-linger admin.
  • Конфликт cron и timer на один скрипт — оставьте один оркестратор.

Профилактика

  • Пара файлов .timer+.service в /etc/systemd/system.
  • Мониторинг LAST и systemctl is-failed backup.service.
  • Не полагаться на точность секунд без AccuracySec=1us (и даже тогда NTP).
  • Документировать Persistent для бэкапов.

FAQ

Чем timer лучше cron?

Календарь, зависимости, cgroup, journal. Cron проще для одной строки. Не мигрируйте всё ради моды.

OnUnitActiveSec=1d от успешного или любого старта?

От активации unit. Если service failed быстро, период всё равно идёт. Читайте man systemd.timer.

Нужен ли WantedBy=timers.target?

Да для enable. Без Install секции enable не привяжет.

Почему apt-daily «случайно»?

RandomizedDelaySec. Не ставьте 0 глобально, получите thundering herd зеркала.

Timer срабатывает дважды

Два unit, или Persistent догон + календарь, или enable в /lib и копия в /etc.