Cron, systemd та планувальники задач на сервері: чому ваш хостинг працює вхолосту 90% часу
Уявіть собі ресторан, де кухар готує всі страви з меню кожні п'ять хвилин - незалежно від того, чи є хоч один відвідувач у залі. Абсурд? Безумовно. Але саме так працює більшість хостинг-серверів, на яких cron-задачі налаштовані "за замовчуванням" і ніхто жодного разу не заглядав у їхній розклад після початкового деплою. Планувальники задач - це нервова система вашого сервера, і якщо вона працює хаотично, весь організм страждає. Тихо, непомітно, але невблаганно.
Що насправді робить ваш сервер, поки ви спите
Більшість адміністраторів впевнені, що їхній сервер "просто працює". Насправді у фоновому режимі відбувається десятки, а іноді сотні запланованих операцій: ротація логів, оновлення SSL-сертифікатів, бекапи баз даних, очищення тимчасових файлів, надсилання email-дайджестів, перевірка оновлень CMS. Кожна з цих задач споживає CPU, оперативну пам'ять і дисковий I/O.
Проблема не в тому, що ці задачі існують, а в тому, що ніхто не контролює, коли і як вони виконуються одночасно.
Класичний приклад. На WordPress-сайті WP-Cron запускає перевірку оновлень плагінів при кожному візиті користувача. Одночасно серверний cron о 3:00 запускає mysqldump для повного бекапу бази. А logrotate вирішує саме в цей момент стиснути 2 ГБ access-логів. Результат - сервер "підвисає" на 15-40 секунд. Якщо у вас інтернет-магазин з нічними покупцями (а вони є завжди) - це втрачені замовлення.

Cron проти systemd timers: академічне порівняння двох філософій
Cron з'явився у Version 7 Unix у 1979 році. Йому 46 років. Він працює. Він простий. І він безнадійно застарів для сучасних завдань - приблизно як факс у світі месенджерів. Systemd timers, що прийшли на зміну у більшості сучасних Linux-дистрибутивів, пропонують принципово інший підхід до планування.
"Cron - це інструмент, який вирішує задачу 1970-х років: запустити щось у визначений час. Systemd timers вирішують задачу 2020-х: запустити щось у визначений час з урахуванням залежностей, ресурсних обмежень і можливості відновлення після збою." - Lennart Poettering, творець systemd, у дискусії на Hacker News, 2021
Давайте порівняємо ці два підходи по конкретних параметрах, що критично важливі для хостинг-серверів:
| Параметр | Cron | Systemd Timers |
|---|---|---|
| Мінімальний інтервал | 1 хвилина | 1 мікросекунда |
| Логування | syslog (загальний потік) | journalctl (ізольоване) |
| Залежності між задачами | Немає | After=, Requires= |
| Обмеження ресурсів | Через nice/ionice вручну | CPUQuota=, MemoryMax= вбудовані |
| Поведінка при пропущеному запуску | Задача просто не виконується | Persistent=true - виконає при старті |
| Рандомізація часу запуску | Немає (всі о 3:00 = всі разом) | RandomizedDelaySec= вбудований |
| Моніторинг статусу | Перевіряти вручну | systemctl status + інтеграція з Prometheus |
Зверніть увагу на рядок "Рандомізація часу запуску". Це та причина, через яку о 3:00 ночі сервери по всьому світу синхронно "гикають" - бо кожен адміністратор вписав у crontab магічне 0 3 * * * для бекапів.
П'ять помилок конфігурації, які перетворюють планувальник на ворога
За 15 років роботи з серверами я бачив одні й ті самі помилки настільки часто, що можу впізнати їх по характерному патерну навантаження в графіках Grafana. Ось вони, ранжовані за ступенем руйнівності:
- Відсутність lock-файлів. Cron не перевіряє, чи завершилась попередня копія задачі. Якщо бекап бази займає 70 хвилин, а cron запускає його щогодини - о 4:00 у вас працюють вже дві копії mysqldump одночасно. О 5:00 - три. Сервер падає о 6:00, коли прокидається ранковий трафік.
- MAILTO не налаштований або ігнорується. Cron відправляє результати виконання на email. Якщо email не налаштований, повідомлення про помилки зникають у порожнечу. Роками.
- Використання абсолютних шляхів без перевірки. Скрипт посилається на /usr/local/bin/php, але після оновлення системи PHP перемістився у /usr/bin/php. Задача тихо падає з exit code 127.
- Ігнорування часових зон. Сервер у Франкфурті, клієнт у Києві, розробник у Торонто. Бекап повинен запускатися "вночі". Чиєї ночі?
- Жодного обмеження ресурсів. Cron-задача з правами root без лімітів на CPU та RAM - це як дати підлітку кредитну картку без ліміту. Рано чи пізно він "вичерпає" все.

Практичний рецепт: як перебудувати розклад задач за одні вихідні
Теорія - прекрасна річ, але вам потрібен конкретний план дій. Ось покроковий алгоритм, який я використовую при аудиті серверів клієнтів. Він займає 4-6 годин і зазвичай зменшує пікове навантаження на 30-50%.
Перший крок - інвентаризація. Зберіть усі cron-задачі з усіх джерел. Це не тільки /var/spool/cron/, а й /etc/cron.d/, /etc/cron.daily/, /etc/cron.hourly/, а також вбудовані планувальники CMS (WordPress, Magento, Drupal). Команда for user in $(cut -f1 -d: /etc/passwd); do crontab -l -u $user 2>/dev/null; done - ваш перший друг.
Другий крок - класифікація за критичністю:
- Критичні - бекапи, оновлення SSL, моніторинг (не можна пропускати, потрібен Persistent=true)
- Важливі - очищення кешу, ротація логів, синхронізація даних (можна затримати на 1-2 години)
- Косметичні - статистика, звіти, оптимізація зображень (можна виконувати у найменш завантажений час)
- Зайві - дубльовані задачі, залишки від старих проектів, тестові скрипти (видалити негайно)
Третій крок - розподіл у часі. Золоте правило: ніколи не ставте дві ресурсомісткі задачі у один п'ятихвилинний слот. Використовуйте RandomizedDelaySec у systemd або додавайте sleep $((RANDOM % 300)) на початку cron-скриптів.
Четвертий і найважливіший крок - впровадьте flock для кожної cron-задачі без винятку. Замість 0 * * * * /scripts/backup.sh пишіть 0 * * * * /usr/bin/flock -n /tmp/backup.lock /scripts/backup.sh. Ця одна зміна запобігає 80% інцидентів, пов'язаних з перевантаженням.
Systemd timers на практиці: міграція без страху
Якщо ви вирішили перейти з cron на systemd timers (а на серверах з Ubuntu 20.04+, Debian 11+ або CentOS Stream 9 це цілком виправдано), процес простіший, ніж здається. Для кожної cron-задачі потрібно створити два файли: .service та .timer.
Приклад. Замість cron-рядка 30 2 * * * /scripts/db-backup.sh ви створюєте:
/etc/systemd/system/db-backup.service - з описом задачі, обмеженнями CPUQuota=25% та MemoryMax=512M. І /etc/systemd/system/db-backup.timer - з OnCalendar=*-*-* 02:30:00, Persistent=true та RandomizedDelaySec=300.
Що ви отримуєте одразу:
- Команда systemctl list-timers показує, коли кожна задача запускалась востаннє і коли запуститься наступного разу
- journalctl -u db-backup.service видає ізольовані логи конкретно цієї задачі - більше ніякого гортання syslog у пошуках голки в стозі сіна
- Якщо сервер був вимкнений о 2:30, задача виконається одразу після запуску (Persistent=true) - cron просто промовчав би
- Задача фізично не може з'їсти більше 25% CPU та 512 МБ пам'яті - навіть якщо скрипт містить баг
Коли планувальник стає архітектором: задачі як код
Найзріліший підхід, який я спостерігаю у командах з DevOps-культурою - це зберігання конфігурації всіх планованих задач у Git-репозиторії разом з інфраструктурним кодом. Ansible, Terraform, навіть простий Makefile - не має значення інструмент. Має значення принцип: якщо конфігурацію cron не можна відтворити з репозиторію за 5 хвилин, її не існує.
Одна команда з Кракова, з якою я працював минулого року, втратила сервер через збій дискового контролера. Відновити дані з бекапу вдалося за годину. Але відтворити розклад з 47 cron-задач, налаштованих вручну за три роки різними людьми - зайняло два тижні. Два тижні неповноцінної роботи сервісу. Після цього інциденту вони перенесли всі задачі у systemd timers, описали їх у Ansible-ролях і тепер повний деплой нового сервера займає 12 хвилин.
Чи перевіряли ви останнім часом, що саме виконує ваш сервер о третій годині ночі? Можливо, він вже роками робить щось, про що ви давно забули - і витрачає на це ресурси, за які ви платите щомісяця. Одна команда crontab -l може відкрити вам більше, ніж будь-який моніторинг. Спробуйте сьогодні ввечері. Результат може вас здивувати.