Systemd timer вместо cron: как перевести бизнес-задачи без простоев
Семёнов Евгений Сергеевич, директор АйТи Фреш. За прошлый год — два одинаковых инцидента у разных клиентов. Cron запускал pg_dump, дамп не успевал завершиться до ночи, и в 00:05 следующего дня cron поднимал второй экземпляр. Оба процесса давились на IOPS, плодили дубли файлов, бэкапили базу по половинкам. Клиенты потом разгребали консистентность несколько часов. Причина банальная — cron не знает, работает ли предыдущая задача. Лечится это переходом на systemd timer, и заодно закрывается ещё несколько хронических болячек. Статья для сисадминов на Linux: как перевести бэкапы, ротации и сервисные задачи — и зачем это вообще затевать.
Почему cron не годится для серьёзных задач
Cron пришёл из эпохи, когда сервер перезагружали раз в год, всё крутилось от root, и вопрос «а что, если задача длится дольше периода?» никого не волновал. В 2026 году его ограничения реально стоят денег:
- Нет защиты от двойного запуска. Задача в 02:00 ещё работает, в 03:00 cron запускает её снова. Приходится городить flock или mkdir-lock — ещё один pid-файл, за которым нужно следить вручную.
- Минимальная гранулярность — минута. Для части сервисных задач этого уже мало.
- Никаких зависимостей. Сначала сеть, потом fstab, потом ваш скрипт — в cron придётся писать sleep или ждать в цикле. В systemd это пара строк
After=network-online.target. - Нет persistent. Сервер был выключен в момент запланированного запуска — задача пропускается, без записи, без уведомления.
- Логи — каждый сам себе писатель. Кто-то пишет в
/var/log/cron.log, кто-то в/dev/null 2>&1, кто-то вMAILTO. Единого места нет.
Systemd timer — это не просто замена cron. Это таймер внутри init-системы, которая знает про сеть, про состояние сервисов, про мониторинг. Он умеет взаимодействовать с остальными компонентами — а не жить в вакууме. Если сегодня разворачиваете новый Linux-сервер, systemd timer — это дефолт, а не «попробуем».
Минимальный пример: бэкап PostgreSQL
Самый частый сценарий в нашей практике. Сервер 1С плюс PostgreSQL, каждую ночь нужен дамп базы и заброс на NAS. Пишем два unit-файла: один для сервиса, второй — для таймера.
# /etc/systemd/system/pg-backup.service
[Unit]
Description=PostgreSQL nightly backup to NAS
Requires=network-online.target
After=network-online.target postgresql.service
[Service]
Type=oneshot
User=postgres
Environment="PGPASSWORD="
ExecStartPre=/usr/bin/test -d /mnt/nas-backups
ExecStart=/usr/local/bin/pg-backup.sh
StandardOutput=journal
StandardError=journal
# Если задача упала — не пытаемся повторить в тот же день
Restart=no
# Лимит по времени (6 часов максимум)
TimeoutStartSec=6h
# /etc/systemd/system/pg-backup.timer
[Unit]
Description=Run PostgreSQL backup every day at 03:00
[Timer]
OnCalendar=*-*-* 03:00:00
# Случайное смещение до 30 минут — чтобы несколько серверов не долбились в NAS одновременно
RandomizedDelaySec=1800
# Если сервер был выключен в 03:00 — догнать после запуска
Persistent=true
Unit=pg-backup.service
[Install]
WantedBy=timers.target
Активация:
systemctl daemon-reload
systemctl enable --now pg-backup.timer
systemctl list-timers | grep pg-backup
Всё. Без единого flock-файла, без pid-файлов, без самодельных блокировок. Журнал автоматически идёт в journald, от двойного запуска защита встроенная, рандомизация — чтобы 15 серверов не долбились в NAS одновременно. Зависимости от сети — тоже.
OnCalendar: синтаксис, который нужно знать
Синтаксис сложнее, чем строка cron — не буду врать. Но гибкость несопоставима. Вот шпаргалка, которую мы раздаём администраторам клиентов при внедрении:
| Задача | OnCalendar |
|---|---|
| Каждую минуту | *:0/1:0 |
| Каждые 15 минут | *:0/15 |
| Каждые 10 секунд | *:*:0/10 |
| Каждый час в 00 минут | hourly или *-*-* *:00:00 |
| Каждый день в 03:00 | *-*-* 03:00:00 |
| По будням в 10:00 | Mon..Fri 10:00 |
| Каждое воскресенье в 04:00 | Sun *-*-* 04:00:00 |
| Первого числа каждого месяца | *-*-01 04:00:00 |
| Каждые 6 часов | *-*-* 00/6:00:00 |
| Каждые 15 секунд | *:*:0/15 |
Проверка синтаксиса без запуска:
systemd-analyze calendar "Mon..Fri 10:00"
# Покажет следующие 3 запуска + оригинальный формат
Защита от двойного запуска — главная фишка
Когда задача запускается как systemd service, она получает имя unit-а. Если unit уже в состоянии active или activating — повторная попытка просто не стартует. На cron для этого ставили flock или mkdir-lock. Тут это работает из коробки, без pid-файлов и без костылей.
Реальный кейс из нашей практики: торговая компания, 38 рабочих мест. Каждые 15 минут синхронизация прайс-листов с 1С на сайт. Python-скрипт, который иногда работает 5 минут, иногда 20. На cron при проблемах с сетью скрипты накапливались — 5-6 процессов ломились в одну таблицу PostgreSQL и создавали deadlock. Перевели на systemd timer. Одна задача за раз. Пропущенные запуски видны в журнале. Нагрузка на 1С упала на 15%.
Persistent: что делать с выключенным сервером
Опция Persistent=true в timer означает: если запланированный запуск был пропущен (сервер выключен, перезагрузка), он выполнится сразу при следующем старте таймера. Для бэкапов и отчётов это спасение.
Конкретный случай: в январе 2026 клиент-медклиника перезагрузила сервер 3 января в 14:00 для обновления. Очередной бэкап 1С был запланирован на 03:00 3 января — в cron-версии он был бы просто потерян. В systemd timer с Persistent=true после перезагрузки timer увидел, что 03:00 уже прошло, стартовал задачу в 14:03. Клиент узнал о «неудачном» окне только по логам, но бэкап состоялся.
Реальные use-case из моей практики
Ротация сертификатов Let's Encrypt
# /etc/systemd/system/acme-renew.timer
[Timer]
OnCalendar=*-*-* 04:30:00
RandomizedDelaySec=3600
Persistent=true
# service вызывает certbot renew с reload nginx через deploy-hook
Очистка журналов WAL PostgreSQL
# /etc/systemd/system/pg-wal-cleanup.timer
[Timer]
OnCalendar=hourly
Unit=pg-wal-cleanup.service
# service запускает pg_archivecleanup с удержанием последних 48h
Мониторинг SSL-сертификатов на всех доменах клиента
# Каждые 6 часов проверка expired
OnCalendar=*-*-* 00/6:00:00
# Если осталось меньше 14 дней — алерт в Telegram
# service: /usr/local/bin/ssl-check --alert-days 14
Ротация логов в приложении
# Каждую ночь + persistent
OnCalendar=daily
Persistent=true
# service: compress + аплоад на S3 + удаление >30d
Обновление GeoIP и Spamhaus zone-list
# Каждые 12 часов с разбросом 2ч
OnCalendar=*-*-* 02,14:00:00
RandomizedDelaySec=7200
Миграция с cron — пошагово
Вот последовательность, которую мы применяем у клиентов при переводе с cron:
- Выгружаем текущий crontab всех пользователей:
for u in $(cut -f1 -d: /etc/passwd); do echo "=== $u ==="; crontab -u $u -l 2>/dev/null; done. - Также смотрим
/etc/cron.{hourly,daily,weekly,monthly}и/etc/cron.d/. - Делим задачи на три группы: критичные (бэкапы, ротация, cleanup), сервисные (GeoIP-обновления, sync-скрипты) и временные — одноразовые, legacy, от прошлых администраторов.
- Для критичных пишем пару unit+timer в
/etc/systemd/system/. Используем готовые шаблоны. - Сервисные — аналогично, но с меньшим приоритетом и менее жёсткими зависимостями.
- Временные и legacy — оставляем в cron как есть. Нет смысла тратить время на задачи, которые скоро уйдут.
- После запуска timer — удаляем соответствующую строку из crontab и ставим комментарий
# migrated to systemd timer /etc/systemd/system/pg-backup.timer. - Через 2 недели проверяем
systemctl list-timers --allи логи — убеждаемся, что всё работает.
Чего не получится сделать
Честно про то, где systemd timer не выигрывает — чтобы не было разочарований:
- Нет концепции MAILTO — если хотите отправлять отчёт по почте, это надо делать внутри скрипта (curl на smtp или через
mail). - Писать unit-файлы дольше, чем строку в crontab. Для разовых задач или простейших скриптов cron элементарно удобнее.
- OnCalendar не умеет «второй вторник месяца» — для таких случаев нужен скрипт с
date-проверкой в ExecStartPre. - На встроенных системах без systemd — роутеры на OpenWrt, некоторые IoT-устройства — таймеров нет вообще. Там cron по-прежнему единственный вариант.
Кейс: миграция 140 cron-джобов у хостинг-клиента
В феврале 2026 года к нам обратилась компания с 14 серверами в двух дата-центрах — Москва и Новосибирск, Debian 12 и Astra Linux 1.7. 1С-кластер, веб-серверы, почтовик, CRM. Суммарно 140 cron-джобов: часть от предыдущего администратора, часть от приходящего подрядчика. Периодически что-то падало. Ночная нагрузка на файловый сервер превышала норму. Бэкапы иногда тихо пропускались — и это обнаруживалось уже при попытке восстановления.
Что мы сделали за 8 рабочих дней:
- Собрали все crontab и
/etc/cron.*в один репозиторий для аудита. Выяснилось: из 140 джобов 26 были дубли, 11 — мёртвые ссылки, 18 — устаревшие. - Написали Ansible-плейбук, который раскладывает шаблоны unit и timer на все 14 серверов за один прогон.
- Мигрировали критические задачи: 32 backup-джоба, 18 cleanup, 14 ротаций сертификатов, 9 sync-скриптов.
- Добавили
RandomizedDelaySecот 5 до 30 минут на все ночные backup-задачи — нагрузка на NAS в окне 02:00-04:00 упала на 40%, бэкапы стали завершаться стабильно. - Настроили Zabbix:
systemctl list-timers --all --no-pager --output=jsonпарсится каждые 15 минут, алерт на любой unit в состоянии failed. - За первый месяц после перехода — ноль дублирующихся запусков, ноль пропущенных бэкапов. Три уведомления о падении скриптов, которые раньше в cron просто проглатывались незаметно.
Стоимость проекта — 145 000 руб. Три backup-задачи, которые раньше падали раз в неделю, теперь стабильны. Однажды клиент уже окупил это вложение: бэкап, который при старой схеме был бы пропущен, оказался нужен — и был на месте.
Переведём ваши cron-задачи на systemd timer — от 18 000 руб.
Я лично планирую и провожу миграцию cron→systemd timer на Linux-серверах в Москве и области. Аудит существующих cron-джобов, разделение на группы, Ansible-шаблоны, интеграция с Zabbix/Prometheus. Типовой проект для 1-5 серверов — 2-3 дня. Для распределённой инфраструктуры — 1-2 недели. Первый аудит бесплатно.
Телефон: +7 903 729-62-41
Telegram: @ITfresh_Boss
Семёнов Евгений Сергеевич, директор АйТи Фреш
FAQ — systemd timer
- В чём главное преимущество systemd timer перед cron?
- Четыре отличия: 1) Защита от двойного запуска — если предыдущая задача ещё работает, новая не стартует. 2) Persistent — пропущенные запуски выполняются после перезагрузки. 3) Логи автоматически идут в journald без ручного перенаправления. 4) Зависимости — задачу можно привязать к готовности сети, монтированию диска, другим сервисам.
- Нужно ли переписывать все cron-джобы сразу?
- Нет. У меня на серверах часто мирно живут оба: systemd timer для критичного (бэкапы, ротация ключей, cleanup), cron для однострочников и старых скриптов. Переход делается поэтапно, начиная с самых болезненных задач — у которых были случаи простоя или дублирования.
- Как проверить статус всех таймеров на сервере?
- Команда systemctl list-timers показывает все активные таймеры, время следующего запуска, время последнего и unit-файл. Для детального просмотра одного — systemctl status backup.timer. Логи выполнения — journalctl -u backup.service.
- Что такое RandomizedDelaySec и зачем оно для бизнеса?
- Случайное смещение внутри окна. Если 20 серверов одновременно в 3:00 ночи делают бэкап на один и тот же файловый сервер, он ложится. RandomizedDelaySec=1800 добавляет к каждому 0-30 минут случайно — нагрузка размазывается. В продовых кластерах это обязательная настройка.
- Systemd timer работает на Astra Linux и РЕД ОС?
- Да, и там, и там systemd используется как init-система, timer-модуль полностью совместим. На Astra Linux 1.7/1.8 и РЕД ОС 8 наши шаблоны timer/service работают без изменений. Это даёт преимущество перед cron при миграции на российский Linux — единый подход на всём парке.
