Плановые задачи — самая недооценённая часть операционной работы. Бэкапы, ротация логов, cleanup, синхронизация — всё это или cron, или systemd timers. Правильный выбор экономит часы поиска «почему не запускается» через полгода.
Разбираемся, чем cron отличается от systemd timers, когда что использовать, и как правильно писать надёжные задачи.
Cron — классика с известными проблемами
Cron — стандарт с 70-х. Работает везде, синтаксис знакомый: 5 3 * * * /usr/local/bin/backup.sh.
Проблемы cron в 2026:
- PATH и env не как у обычного пользователя — скрипт может работать из shell, но падать из cron из-за отсутствия $PATH.
- Логирование в /var/log/syslog только сам факт запуска. stdout/stderr скрипта уходит в письмо root@localhost, которое обычно никто не читает.
- Нет зависимостей — нельзя сказать «запусти после NetworkManager» или «только если mysql жив».
- Нет retries при провале.
- Overlap — если предыдущий запуск не завершился, следующий запустится тоже, и они начнут конфликтовать.
- Учёт пропущенных запусков — если сервер был выключен на время cron-задачи, она просто не выполнится.
Systemd timers — современная альтернатива
Systemd timers решают почти все проблемы cron ценой некоторой verbose-ности конфигурации. Есть на всех дистрибутивах с systemd (Ubuntu 16.04+, Debian 8+, CentOS 7+).
Плюсы:
- Логи в journald —
journalctl -u mytask.serviceпокажет весь stdout/stderr. - Зависимости —
After=network.target mysql.serviceгарантирует порядок. - Persistent —
Persistent=trueзапустит задачу, если пропущен запуск (после выключения). - Anti-overlap — по умолчанию, service не запустится дважды, если предыдущий не завершился.
- Мониторинг — status service показывает: активен, когда последний запуск, когда следующий.
- Randomization —
RandomizedDelaySecрассеивает нагрузку от одинаковых задач на нескольких серверах.
Практика: cron
Пример backup-cron с типовыми предосторожностями:
# /etc/cron.d/backup — редактируется прямо в файле
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
[email protected]
# Backup каждый день в 3:15 ночи
15 3 * * * root flock -n /var/lock/backup.lock /usr/local/bin/backup.sh >>/var/log/backup.log 2>&1
# flock -n не даст запуститься второму экземпляру
# 2>&1 в log — иначе всё уходит в почту rootПрактика: systemd timer
Тот же backup через systemd — два файла:
# /etc/systemd/system/backup.service
[Unit]
Description=Nightly backup
After=network-online.target mysql.service
Wants=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup.sh
User=backup
StandardOutput=journal
StandardError=journal
Environment="AWS_ACCESS_KEY_ID=..." "AWS_SECRET_ACCESS_KEY=..."
# /etc/systemd/system/backup.timer
[Unit]
Description=Nightly backup timer
[Timer]
OnCalendar=daily
AccuracySec=1min
RandomizedDelaySec=15min
Persistent=true
[Install]
WantedBy=timers.target
# Активация:
systemctl daemon-reload
systemctl enable --now backup.timer
systemctl list-timersКогда что использовать
Cron — если:
- Задача простая (сдёрнуть логи, ротация).
- Проект использует несколько дистрибутивов (в т.ч. BSD/Alpine — там нет systemd).
- Команда привыкла к cron.
Systemd timers — если:
✅ Нужна нормальная логика (retry, dependencies, logs)
✅ VPS иногда выключается — важен Persistent
✅ Много одинаковых серверов — RandomizedDelaySec
✅ Мониторинг: journalctl + Prometheus systemd_exporterСовет по надёжности
Что бы вы не выбрали:
- Логируйте всё — stdout+stderr в файл или journald.
- Мониторьте — не полагайтесь только на «раз в день». Уведомляйте через Healthchecks.io: скрипт пингует URL при успехе, healthchecks шлёт alert, если пинг пропущен.
- Не пишите бизнес-логику в скрипте на 500 строк — вынесите в отдельный package.
- Timeout — обязательный (
timeout 3600 ./backup.sh) — иначе застрявший процесс будет висеть неделями.
Healthchecks.io free-план поддерживает 20 checks — достаточно для среднего проекта. Отправляйте curl из скрипта при успехе, получайте alert при отсутствии пинга.