Systemd timer вместо cron: как перевести бизнес-задачи без простоев
Меня зовут Семёнов Евгений Сергеевич, директор АйТи Фреш. За последний год у меня дважды был один и тот же инцидент у разных клиентов: cron запустил pg_dump, дамп не доделался за ночь, в 00:05 следующего дня cron запустил второй экземпляр — и оба задыхались на IOPS, создав дубли файлов и выполнив backup дважды на половинах базы. Оба клиента потом долго восстанавливали консистентность. Решается проблема заменой cron на systemd timer — и заодно закрываются ещё несколько болячек, о которых я расскажу ниже. Это статья для системных администраторов среднего бизнеса на Linux-серверах: как перевести ваши бэкапы, ротации и сервисные задачи и зачем это стоит затевать.
Почему cron не годится для серьёзных задач
Cron — это инструмент из эпохи, когда сервер перезагружался раз в год, всё запускалось от root, и никому не было дела до «что будет, если задача длится дольше периода». В 2026 году его ограничения стоят денег и времени:
- Нет защиты от двойного запуска. Если задача в 02:00 ещё работает, а следующий запуск в 03:00 — cron запустит её параллельно. Приходилось костылить flock, а это очередной pid-файл, за которым нужно следить.
- Минимальная гранулярность — минута. Для многих сервисных задач этого мало.
- Никаких зависимостей. Сначала сеть, потом fstab, потом ваш скрипт — в cron придётся писать sleep или ждать в цикле. В systemd это пара строк
After=network-online.target. - Нет persistent — если сервер был выключен в момент запланированного запуска, задача пропускается без следа.
- Логи — каждый сам себе писатель. Кто-то пишет в
/var/log/cron.log, кто-то в/dev/null 2>&1, кто-то вMAILTO. Единого места нет.
Systemd timer — это таймер как часть init-системы. Он знает про все сервисы, состояние сети, мониторинг и умеет взаимодействовать с остальными компонентами. Если вы в 2026 году разворачиваете новый сервер — 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
Всё — заодно получили журнал в journald, защиту от двойного запуска, рандомизацию, зависимости, persistent. Без единого flock-файла.
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. Тут — бесплатно.
Пример из недавнего кейса: клиент — торговая компания, 38 рабочих мест, каждые 15 минут синхронизация прайс-листов с 1С на сайт. Скрипт на python, sometimes 20 минут, sometimes 5. На cron при проблеме с сетью скрипты накапливались, 5-6 процессов долбились в одну и ту же таблицу PostgreSQL, создавали deadlock. После перевода на 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 — пошагово
План, который мы применяем у клиентов при переводе:
- Выгружаем текущий 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и логи — убеждаемся, что всё работает.
Чего не получится сделать
Честно про ограничения, чтобы не разочаровывать:
- Нет концепции MAILTO — если хотите отправлять отчёт по почте, это надо делать внутри скрипта (curl на smtp или через
mail). - Писать unit-файлы — дольше, чем строку cron. Для одноразовых задач cron проще.
- OnCalendar не умеет «второй вторник месяца» — для таких случаев нужен скрипт с
date-проверкой в ExecStartPre. - На очень легковесных системах (роутеры с OpenWrt, встроенные системы без systemd) таймера нет вообще. Там 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 syncs.
- Добавили
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 руб. Три старых бэкапа, которые раньше падали раз в неделю, теперь стабильны. Один раз у клиента это уже сэкономило день работы — бэкап, который бы пропустили, оказался востребован.
Переведём ваши 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 — единый подход на всём парке.