· 14 мин чтения

systemd timers вместо cron: почему я перевёл все свои серверы

Меня зовут Семёнов Евгений Сергеевич, директор АйТи Фреш. За 15+ лет работы с Linux я настолько привык к cron, что годами игнорировал systemd timers как «модное новое». Переломный момент настал, когда на одном из клиентских серверов после долгого даунтайма три критичных cron-задания были пропущены за ночь — бэкап не сделался, nightly-отчёт не отправился, SSL-сертификаты не обновились. systemd timer с опцией Persistent решил бы это одной строкой. С тех пор я планомерно перевожу клиентов на timers, и в этой статье расскажу, почему.

Что не так с cron в 2025 году

Cron — легенда, но легенда с возрастом. Основные боли, которые я встречал у клиентов:

Всё это systemd решает нативно. У нас на практике переход на timers снижает количество «магических» инцидентов с задачами в 2-3 раза.

Анатомия systemd timer

systemd timer — это два файла: .timer (когда запускать) и .service (что запускать). Пример — ежедневный бэкап в 3:15:

# /etc/systemd/system/backup.service
[Unit]
Description=Daily backup of /srv
Wants=network-online.target
After=network-online.target

[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup.sh
Nice=19
IOSchedulingClass=idle
MemoryMax=2G
CPUQuota=50%
PrivateTmp=true
NoNewPrivileges=true

[Install]
WantedBy=multi-user.target
# /etc/systemd/system/backup.timer
[Unit]
Description=Run backup.service daily at 03:15
Requires=backup.service

[Timer]
OnCalendar=*-*-* 03:15:00
RandomizedDelaySec=900
Persistent=true
Unit=backup.service

[Install]
WantedBy=timers.target
sudo systemctl daemon-reload
sudo systemctl enable --now backup.timer
sudo systemctl list-timers --all

Ключевые опции OnCalendar

OnCalendar принимает мощный синтаксис. Примеры:

ВыражениеЗначение
dailyКаждый день в 00:00
hourlyКаждый час
*-*-* 03:15:00Каждый день в 3:15
Mon..Fri 09:00Каждый будний день в 9:00
*-*-01 00:00:00Первого числа каждого месяца
*-*-* *:00/15Каждые 15 минут
*-*-* *:*:30Каждую минуту в 30 секунд

Полезная утилита для проверки: systemd-analyze calendar 'Mon..Fri 09:00' покажет следующие 3-5 запусков.

Persistent: догнать пропущенное

Это моя любимая опция. Если сервер был выключен или timer был disabled в момент запланированного запуска, с Persistent=true задача выполнится сразу после возобновления работы. systemd пишет state-файл в /var/lib/systemd/timers/ и сверяется с ним при старте.

Типичный сценарий: вы отключили сервер на 6 часов для обслуживания в субботу утром. С cron задачи, запланированные на это время, пропали бесследно. С systemd Persistent — выполнятся при старте сервера, возможно с небольшой задержкой.

Мониторинг: journalctl как native-логи

В cron вы бы боролись с MAILTO или >> /var/log/myjob.log. В systemd всё проще:

# Последний запуск backup.service
journalctl -u backup.service -n 100

# Все запуски за последние сутки
journalctl -u backup.service --since '24 hours ago'

# Следить в реальном времени
journalctl -u backup.service -f

# Только ошибки
journalctl -u backup.service -p err

Вывод скрипта (stdout/stderr) автоматически попадает в журнал. Код возврата тоже — при падении вы увидите Main process exited, code=exited, status=1/FAILURE.

Ограничения ресурсов: sandboxing из коробки

В cron job может внезапно съесть 16 ГБ RAM или залить CPU на 100% и положить сервер. В systemd service это решается декларативно:

[Service]
# Память
MemoryMax=2G
MemoryHigh=1.5G

# CPU
CPUQuota=50%
CPUWeight=50

# I/O
IOWeight=10

# Сеть (через slice или firewall-direct)
IPAccounting=true

# Изоляция
PrivateTmp=true
ProtectSystem=strict
ProtectHome=read-only
NoNewPrivileges=true
ReadWritePaths=/var/backup /var/log/backup

Это защищает сервер от runaway-задач без отдельных cgroup-настроек.

Сабминутные задачи через OnUnitActiveSec

cron не умеет «каждые 30 секунд». systemd timer — пожалуйста:

[Timer]
OnBootSec=30s
OnUnitActiveSec=30s
Unit=my-monitor.service

Подходит для healthcheck'ов, мониторинга очередей, polling API.

Миграция существующего crontab

Пошаговый план, которым я пользуюсь на клиентских серверах:

  1. crontab -l и ls /etc/cron.{d,daily,weekly,hourly} — собираем полный список текущих задач.
  2. Для каждой задачи создаём пару .service + .timer в /etc/systemd/system/.
  3. Тестируем каждый сервис вручную: systemctl start myjob.service.
  4. Активируем таймер: systemctl enable --now myjob.timer.
  5. Проверяем следующие запуски: systemctl list-timers.
  6. Через 24-48 часов убеждаемся, что всё работает, и только тогда удаляем из cron.

Пользовательские таймеры без root

Нужна задача, которая не требует root, и вы не хотите править /etc/?

mkdir -p ~/.config/systemd/user
# Создать файлы ~/.config/systemd/user/myjob.service и myjob.timer
systemctl --user daemon-reload
systemctl --user enable --now myjob.timer

# Чтобы работало после выхода пользователя
loginctl enable-linger myuser

Реальный кейс: миграция инфраструктуры медицинского центра

В сентябре 2025 мы переводили клиента — сеть медицинских центров в Москве, 9 филиалов, головной офис на 120 рабочих мест — с легаси-cron на systemd timers. На каждом сервере филиала было от 15 до 40 cron-задач: синхронизация пациентских данных, выгрузка в ФОМС, бэкапы, отчёты, обновления справочников.

Проблема была в том, что раз в неделю кто-то из администраторов жаловался «у нас снова не отправился отчёт в ФОМС». Оказалось — ночью сервер ребутился для обновления, и crontab пропустил задачу 3:30. С Persistent=true эта проблема исчезла.

Сервера — Dell PowerEdge R740 с Xeon Gold 6248, 64 ГБ RAM, хранилище на NVMe в RAID-10, подключены 40G Mellanox в дата-центр МТС. Миграцию 11 серверов сделали за 4 рабочих дня. Стандартизировали юниты через Ansible-роль, подключили journald с пересылкой в Graylog. За полгода после миграции — ни одного инцидента с пропущенными задачами. Стоимость работ — 78 000 руб.

Грабли и особенности

Переведём ваши задачи с cron на systemd timers

Проанализируем текущие crontab, спроектируем systemd-юниты с Persistent, лимитами ресурсов и правильной обработкой зависимостей. Интеграция с journald и Graylog. Работаем через Ansible — для унифицированного деплоя на несколько серверов.

Телефон: +7 903 729-62-41
Telegram: @ITfresh_Boss
Семёнов Евгений Сергеевич, директор АйТи Фреш

FAQ — частые вопросы о systemd timers

Зачем отказываться от cron?
Лучшая интеграция с логами, зависимости, ограничения ресурсов, Persistent для пропущенных запусков, сабминутные интервалы.
Где хранить systemd-юниты?
Системные — в /etc/systemd/system/, пользовательские — в ~/.config/systemd/user/, дистрибутивные — /usr/lib/systemd/system/.
Как запускать задачу раз в 30 секунд?
OnUnitActiveSec=30s в секции [Timer].
Где смотреть вывод задач?
journalctl -u my-job.service — покажет всё: stdout, stderr, код возврата.
Что делать с существующим crontab?
Постепенно мигрировать, начиная с критичных задач. Cron и systemd timers уживаются без конфликтов.

Подпишитесь на рассылку ITfresh

Раз в неделю — практические гайды для руководителя IT и сисадмина: безопасность, 1С, миграции, резервные копии, лайфхаки из реальных проектов.

Реквизиты оператора персональных данных

ООО «АЙТИ-ФРЕШ», ИНН 7719418495, КПП 771901001. Юридический адрес: 105523, г. Москва, Щёлковское шоссе, д. 92, корп. 7. Контакт: info@itfresh.ru, +7 903 729-62-41. Оператор обрабатывает e-mail подписчика в целях рассылки информационных и рекламных материалов до момента отзыва согласия.