12 октября 2026 г.

Таймеры systemd вместо cron: как запускать задачу по расписанию и видеть, что она сработала

Cron тихо пропустил запуск, и никто не заметил. Настраиваем таймер systemd: логи, догон после простоя и понятное время следующего запуска.

Знакомая история: ночной бэкап сидит в cron, все ему доверяют, а через три недели выясняется, что с перезагрузки сервера в 03:29 он ни разу не запускался. Cron работает, но почти ничего не рассказывает. Таймер systemd делает ту же работу и добавляет лог, понятное время следующего запуска и догон после простоя. Ниже рабочая пара файлов и то, что стоит проверить после включения.

Коротко. Таймер - это два маленьких файла: имя.service говорит, что запускать, имя.timer - когда. Включается командой sudo systemctl enable --now имя.timer. systemctl list-timers показывает следующий запуск, journalctl -u имя.service - что выводила задача. Persistent=true догоняет пропущенный запуск после простоя, RandomizedDelaySec размазывает нагрузку. Для разовой мелочи cron по-прежнему подходит.

Чем cron плох на сервере

Cron - планировщик из семидесятых: одна строка в таблице, пять полей времени и команда. Он короткий и всем знаком, поэтому им продолжают пользоваться. Проблемы начинаются, когда задача важная:

  • Нет памяти о том, что произошло. Вывод уходит в локальную почту, а без почтового сервера просто пропадает. Вы дописываете >> /var/log/job.log 2>&1 и надеетесь.
  • Нет догона. Если сервер был выключен или перезагружался в 03:30, запуск пропущен. Классический cron позже его не повторит (для этого есть anacron, но это отдельная программа).
  • Голое окружение. Cron запускает задачи с минимальным PATH и без ваших настроек оболочки, поэтому скрипт, который работает в терминале, ночью падает с «command not found».
  • Нет простого ответа на вопрос «когда следующий запуск». Читаете таблицу и считаете в уме.

Таймер systemd закрывает каждый пункт без дополнительных программ: systemd на вашем сервере и так работает первым процессом.

Шаг 1. Пишем службу: что запускать

Сначала файл .service. Он описывает одну задачу, а не постоянно работающую программу: запустить скрипт, дождаться выхода, готово. Именно это говорит Type=oneshot. Кладём файл в /etc/systemd/system/:

В ExecStart пишите полный путь: systemd не ищет программу по PATH, как это делает оболочка. Сделайте скрипт исполняемым: sudo chmod +x /usr/local/bin/backup.sh. Всё, что скрипт выводит через echo, автоматически попадает в журнал, перенаправлять вывод в файл не нужно.

Шаг 2. Пишем таймер: когда запускать

Файл .timer должен называться так же, как служба (backup.timer запускает backup.service). Этот запускает задачу каждую ночь в 03:30:

Что делает каждая строка:

  • OnCalendar - расписание в виде день_недели год-месяц-день часы:минуты:секунды. Звёздочка означает «любое», так что *-*-* 03:30:00 - каждый день в 03:30. Время берётся по часовому поясу сервера.
  • RandomizedDelaySec=300 сдвигает каждый запуск на случайную задержку до пяти минут. Если серверов много, они не ударят по вашему хранилищу в одну и ту же секунду.
  • Persistent=true запоминает время последнего запуска на диске. Если в 03:30 сервер был выключен, задача запустится сразу после загрузки.
  • WantedBy=timers.target заставляет включённый таймер подниматься после каждой перезагрузки.

Не уверены, что расписание означает то, что вы думаете? Попросите systemd перевести его до включения чего-либо:

Этого у cron не было никогда: вы видите настоящий момент следующего запуска, а не пять полей. Работают и короткие имена: daily, weekly, hourly.

Шаг 3. Включаем и смотрим следующий запуск

Сначала проверим файлы на опечатки. Включаем именно .timer, а не службу:

sudo systemd-analyze verify /etc/systemd/system/backup.service /etc/systemd/system/backup.timer

Если всё в порядке, команда ничего не выведет. Теперь перечитаем файлы и включим таймер:

sudo systemctl daemon-reload && sudo systemctl enable --now backup.timer

Спросим про расписание:

Одна строка отвечает на вопрос, на который cron ответить не мог: следующий запуск завтра в 03:31:44. Лишние полторы минуты - случайная задержка от RandomizedDelaySec. Колонка LAST пуста, потому что задача ещё не запускалась.

Шаг 4. Запускаем один раз вручную и читаем лог

Не ждите ночи, чтобы найти опечатку. Запустите службу напрямую, расписание от этого не изменится:

Это тот лог, которого у cron не было: время старта, всё, что напечатал скрипт, и вердикт. Deactivated successfully значит код выхода 0. Если скрипт упадёт, на том же месте появится Failed with result 'exit-code', а systemctl status backup.service покажет код выхода.

cron или таймер: как выбрать

cron

таймер systemd

Настройка

одна строка в crontab -e

два файла и одна команда

Лог

вывод перенаправляете сами

в журнале по умолчанию

Пропущенный запуск после простоя

теряется

запускается после загрузки при Persistent=true

Время следующего запуска

считаете сами

systemctl list-timers

Окружение

минимальный PATH

задаётся явно через Environment= или EnvironmentFile=

Наложение запусков

стартует вторая копия, помогает flock

по умолчанию работающий юнит повторно не запускается

Работает без systemd

да

нет

Наше правило: если задача важная (бэкап, продление сертификата, очистка, которая освобождает диск), берите таймер. Если это разовая мелочь вроде «дёргать адрес раз в пять минут», cron подойдёт и напишется быстрее. Многие системные задачи самой Ubuntu уже переехали на таймеры: на свежем сервере systemctl list-timers покажет обновления apt, ротацию логов и продление сертификатов.

Как удалить таймер

Отключите таймер, удалите файлы и перечитайте конфигурацию:

sudo systemctl disable --now backup.timer && sudo rm /etc/systemd/system/backup.{service,timer} && sudo systemctl daemon-reload

Частые вопросы

Таймер systemd лучше cron?

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

Как запускать таймер каждые 15 минут?

Напишите OnCalendar=*:0/15 для «каждую четверть часа по часам» или OnBootSec=5min вместе с OnUnitActiveSec=15min для «через 15 минут после предыдущего запуска».

Почему мой таймер не сработал?

Проверьте, что включён именно .timer, а не .service, что systemctl list-timers его показывает и что в journalctl -u имя.service нет ошибок. Сверьте и часовой пояс сервера: OnCalendar использует его.

Что делает Persistent=true?

Сохраняет время последнего запуска на диск. Если запланированный момент прошёл, пока сервер был выключен, задача запустится сразу после следующей загрузки.

Можно оставить crontab и добавить таймеры?

Можно, они работают рядом. Переносите те задачи, где нужны логи и догон, остальные оставьте.

Итог

  • Таймер - это .service (что запускать, Type=oneshot) плюс .timer (когда), включается через systemctl enable --now имя.timer.
  • systemd-analyze calendar показывает, когда расписание реально сработает, systemctl list-timers - что запланировано.
  • Вывод задачи лежит в journalctl -u имя.service, перенаправлять в файл не нужно.
  • Persistent=true спасает запуск, пропущенный во время простоя, RandomizedDelaySec размазывает нагрузку по серверам.
  • Важное - в таймеры, разовую мелочь можно оставить в cron.
PUBLISHED
12 октября 2026 г.
AUTHOR
HIP-HOSTING
LANGUAGES
EN · RU