Ви запускаєте скрипт вручну. Він працює. Ви ставите його в cron. Він зникає в ніч, наче колега, коли дзвонить черговий телефон.
На Ubuntu 24.04 це зазвичай не означає, що «cron зламався». Питання в тому, що cron безжально чесний: він виконує вашу команду з мінімальним набором змінних середовища, в неінтерактивному шеллі, з іншими очікуваннями щодо робочого каталогу і без поблажок до «в мене в терміналі все працює». Зробимо cron нудним знову.
Що змінюється, коли cron виконує вашу команду
Коли ви запускаєте команду вручну, ви приносите із собою багато невидимого багажу: інтерактивний shell, ваші dotfiles, налаштування PATH, поточний каталог, завантажений SSH-агент, можливо Kerberos-квиток і та одну експортовану змінну з минулого тижня, яку ви забули.
Cron нічого з цього не приносить. Світогляд cron ближчий до: «Ось час. Ось рядок з командою. Я виконую його з невеликим набором змінних середовища і не вгадую, що ви мали на увазі.» Це не злість — це надійність.
На Ubuntu cron зазвичай використовує /bin/sh (dash), а не bash, якщо ви явно не вказали інше. Він працює неінтерактивно. Він не читає ваш ~/.bashrc. За замовчуванням PATH невеликий (часто /usr/bin:/bin плюс кілька sbin-шляхів залежно від контексту). І якщо ви покладаєтеся на відносні шляхи, cron із задоволенням запустить процес з домашнього каталогу або з /, залежно від того, як робота викликана, і ваші відносні шляхи поведуться як лотерея.
Операційне правило: ставтеся до cron як до маленького контейнера без особистості. Якщо вашій задачі щось потрібно — оголосіть це явно.
Швидкий план діагностики
Коли ви на чергуванні, немає часу на філософські дебати з cron. Потрібен найкоротший шлях до істини. Ось порядок перевірок, який зазвичай найшвидше виявляє вузьке місце.
1) Доведіть, що cron намагався виконати
- Перевірте стан служби cron (systemd status).
- Перегляньте журнали на точну хвилину, коли задача мала запуститися.
- Підтвердіть, що ви редагували потрібний crontab (користувача vs root vs /etc/cron.*).
2) Збережіть середовище й stderr/stdout
- Перенаправте вивід у лог-файл з позначками часу.
- Скиньте
envу файл під час виконання задачі. - Використовуйте абсолютні шляхи для всього: інтерпретатора, скрипту, бінарів, файлів.
3) Усуньте відмінності PATH/шелла
- Задайте відомий PATH у crontab (або на початку скрипту).
- Примусьте використовувати bash, якщо ви написали bash-особливості.
- Обережно використовуйте
/usr/bin/env(PATH у cron може не містити того, що ви очікуєте).
4) Перевірте дозволи та ідентичність
- Переконайтеся, що задача виконується від імені того користувача, якого ви очікуєте.
- Перевірте членство в групах і дозволи файлів.
- Слідкуйте за тихими помилками sudo (cron не має TTY).
5) Перевірте час і особливості розкладу
- Розбіжність часових зон (система vs очікування користувачів).
- Проблеми з DST (завдання навколо 02:00 прокляті двічі на рік).
- Помилка в синтаксисі cron: логіка day-of-month vs day-of-week.
Цікаві факти та історичний контекст
- «Мінімальне середовище» cron — це перевага, а не баг. Ранні Unix-автоматизації вимагали від скриптів явно оголошувати залежності, бо логін-шели були різними для користувачів.
- /bin/sh в Ubuntu — це dash, а не bash, для швидкості. Це рішення існує роками і все ще дивує скрипти, які «в інтерактиві працювали нормально».
- Традиційний cron існує задовго до systemd. Ментальна модель cron — «запустити команду в час», тоді як systemd timers додають «виконувати з порядком залежностей і журналюванням через journald».
- Vixie Cron сформував сучасну екосистему cron. Багато дистрибутивів Linux успадкували його поведінку, включно з патерном доставки виводу через MAILTO.
- Формат розкладу cron навмисно компактний. Він оптимізований під друк людиною, а не під відлагодження людиною; через це помилки у полях трапляються часто.
- Історично cron логував у syslog. На сучасній Ubuntu syslog може існувати, але journald часто є першим джерелом правди.
- Є кілька точок входу для cron. Користувацькі crontab-и,
/etc/crontabі каталоги/etc/cron.*працюють по-різному (зокрема: поле користувача у системному crontab). - Anacron існує через те, що ноутбуки засинають. «Запускати щоденні задачі навіть якщо машина була вимкнена» вирішив реальну проблему 1990-х і досі важливий для переривчастих серверів і віртуалок.
- Деякі реалізації cron підтримують секунди; класичний cron — ні. Якщо потрібен інтервал менше хвилини, ви вже поза зоною комфорту cron.
Практичні завдання (команди, виводи, рішення)
Нижче — перевірки, перевірені в полі. Кожна включає реалістичний приклад виводу і що з цього випливає. Виконуйте їх у черговості, коли можете; вибирайте необхідні, якщо часу обмаль.
Завдання 1: Переконайтеся, що служба cron працює
cr0x@server:~$ systemctl status cron
● cron.service - Regular background program processing daemon
Loaded: loaded (/usr/lib/systemd/system/cron.service; enabled; preset: enabled)
Active: active (running) since Mon 2025-12-29 08:11:02 UTC; 3h 14min ago
Docs: man:cron(8)
Main PID: 742 (cron)
Tasks: 1 (limit: 18639)
Memory: 2.2M
CPU: 1.023s
CGroup: /system.slice/cron.service
└─742 /usr/sbin/cron -f
Що це означає: Якщо він не active (running), ваша задача навіть не мала шансу. Якщо ж активний — продовжуйте діагностику.
Рішення: Якщо inactive — запустіть/увімкніть службу. Якщо active — помилка всередині планування, середовища, дозволів або вашого скрипту.
Завдання 2: Перевірте, що ви редагували потрібний crontab
cr0x@server:~$ crontab -l
# m h dom mon dow command
*/5 * * * * /home/cr0x/bin/report.sh
Що це означає: Це crontab поточного користувача. Root має окремий crontab.
Рішення: Якщо ви мали на увазі root — виконайте sudo crontab -l. Якщо потрібен системний — перевірте /etc/crontab і /etc/cron.d.
Завдання 3: Перегляньте журнали cron за хвилину запуску (journald)
cr0x@server:~$ sudo journalctl -u cron --since "2025-12-29 10:00" --until "2025-12-29 10:10"
Dec 29 10:05:01 server CRON[18342]: (cr0x) CMD (/home/cr0x/bin/report.sh)
Dec 29 10:05:01 server CRON[18341]: (CRON) info (No MTA installed, discarding output)
Що це означає: Cron дійсно виконав команду о 10:05. Також він відкинув вивід, бо MTA не встановлено.
Рішення: Якщо немає рядка «CMD» у очікуваний час — проблема з плануванням/встановленням. Якщо команда виконана — тепер потрібно самим зберегти stdout/stderr.
Завдання 4: Перевірте записи у стилі syslog (якщо використовується rsyslog)
cr0x@server:~$ grep -i cron /var/log/syslog | tail -n 5
Dec 29 10:05:01 server CRON[18342]: (cr0x) CMD (/home/cr0x/bin/report.sh)
Dec 29 10:00:01 server CRON[18112]: (cr0x) CMD (/home/cr0x/bin/report.sh)
Що це означає: Та сама істина, що й у journald, але з syslog. Деякі середовища зберігають обидва; деякі — ні.
Рішення: Якщо у /var/log/syslog немає записів про cron — не панікуйте; покладіться на journald.
Завдання 5: Додайте явне логування до запису crontab
cr0x@server:~$ crontab -l | tail -n 3
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
*/5 * * * * /home/cr0x/bin/report.sh >>/var/log/report.log 2>&1
Що це означає: Ви примусово встановили shell і PATH, та зберігаєте вивід.
Рішення: Якщо задача «нічого не робить», це створить артефакт. Якщо лог лишається порожнім — вона не виконується (або не може відкрити шлях до логу).
Завдання 6: Переконайтеся, що лог-файл записуваний цим користувачем
cr0x@server:~$ ls -l /var/log/report.log
-rw-r--r-- 1 root root 0 Dec 29 10:04 /var/log/report.log
Що це означає: Ваш користувач за замовчуванням не може додавати в файл, що належить root у /var/log.
Рішення: Логуйте в шлях, що належить користувачу (наприклад ~/cron-logs) або налаштуйте правильні дозволи/logrotate. Не «вирішуйте» це chmod 777, якщо вам не подобаються інцидентні розбори.
Завдання 7: Збережіть середовище виконання cron
cr0x@server:~$ mkdir -p /home/cr0x/cron-debug
cr0x@server:~$ crontab -l | tail -n 2
*/5 * * * * env | sort > /home/cr0x/cron-debug/env.txt
*/5 * * * * /usr/bin/date -Is >> /home/cr0x/cron-debug/ticks.log 2>&1
cr0x@server:~$ cat /home/cr0x/cron-debug/env.txt
HOME=/home/cr0x
LANG=C.UTF-8
LOGNAME=cr0x
PATH=/usr/bin:/bin
PWD=/home/cr0x
SHELL=/bin/sh
USER=cr0x
Що це означає: PATH у cron мінімальний, а shell — /bin/sh.
Рішення: Якщо ваш скрипт покладається на /usr/local/bin, ~/.local/bin, pyenv, rbenv, nvm, conda тощо — тепер зрозуміло, чому він падає за розкладом.
Завдання 8: Доведіть, що помилка — «command not found»
cr0x@server:~$ tail -n 5 /home/cr0x/cron-logs/report.log
/home/cr0x/bin/report.sh: line 7: jq: command not found
Що це означає: Інтерактивний shell знаходить jq, а cron — ні.
Рішення: Використайте абсолютний шлях (/usr/bin/jq) або встановіть PATH у crontab/скрипті. Також перевірте, що пакет дійсно встановлений там, де ви думаєте.
Завдання 9: Знайдіть бінарні файли так, як це робить cron
cr0x@server:~$ command -v jq
/usr/bin/jq
cr0x@server:~$ /usr/bin/env -i PATH=/usr/bin:/bin command -v jq
/usr/bin/jq
cr0x@server:~$ /usr/bin/env -i PATH=/usr/bin:/bin command -v aws
/usr/bin/env: ‘command’: No such file or directory
Що це означає: В очищеному середовищі навіть command може не існувати як окремий бінарник; це — вбудована функція шеллу. Також інструменти, встановлені в користувацьких шляхах, не будуть знайдені.
Рішення: Тестуйте скрипт у «очищеному» середовищі. Нічому не довіряйте. Якщо вам потрібен aws з ~/.local/bin, задайте PATH або викликайте його явно.
Завдання 10: Запустіть скрипт так, як це робить cron (неінтерактивно, мінімальне середовище)
cr0x@server:~$ /usr/bin/env -i HOME=/home/cr0x USER=cr0x LOGNAME=cr0x PATH=/usr/bin:/bin SHELL=/bin/sh /bin/sh -c '/home/cr0x/bin/report.sh' ; echo $?
127
Що це означає: Код виходу 127 — класичний «command not found». Це чисте відтворення, яке вам потрібно.
Рішення: Виправте залежності (встановіть відсутні пакети, використайте абсолютні шляхи або встановіть PATH). Більше не відлагоджуйте це у вашому інтерактивному shell — він вам брешуть.
Завдання 11: Переконайтеся, що скрипт має валідний shebang і є виконуваним
cr0x@server:~$ head -n 1 /home/cr0x/bin/report.sh
#!/usr/bin/env bash
cr0x@server:~$ ls -l /home/cr0x/bin/report.sh
-rwxr-xr-x 1 cr0x cr0x 1842 Dec 29 09:55 /home/cr0x/bin/report.sh
Що це означає: Він виконуваний і має shebang. Але зауважте: /usr/bin/env шукає bash у PATH. У cron PATH може не містити місця, де знаходиться bash (зазвичай містить), але будьте явними, якщо ви хочете зміцнити захист.
Рішення: Для продакшн-завдань у cron краще використовувати #!/bin/bash, якщо ви покладаєтеся на функції bash. Це нудно, але стабільно.
Завдання 12: Переконайтеся, що розклад такий, як ви думаєте
cr0x@server:~$ grep -n . /var/spool/cron/crontabs/cr0x | sed -n '1,5p'
1 # DO NOT EDIT THIS FILE - edit the master and reinstall.
2 # m h dom mon dow command
3 5 * * * * /home/cr0x/bin/report.sh
Що це означає: Це запускається на 5-й хвилині кожної години, а не кожні 5 хвилин. Люди постійно неправильно читають це.
Рішення: Виправте розклад. Якщо ви мали на увазі кожні 5 хвилин — використайте */5 * * * *.
Завдання 13: Перевірте припущення про часовий пояс
cr0x@server:~$ timedatectl
Local time: Mon 2025-12-29 11:25:41 UTC
Universal time: Mon 2025-12-29 11:25:41 UTC
RTC time: Mon 2025-12-29 11:25:41
Time zone: Etc/UTC (UTC, +0000)
System clock synchronized: yes
NTP service: active
Що це означає: Сервер налаштований на UTC. Якщо люди очікують локальний час, задача «промахнеться» на кілька годин і всі знову звинуватять cron.
Рішення: Вирішіть, чи хочете ви системний час в UTC (зазвичай так), і відкоригуйте розклад, або змініть очікування на UTC.
Завдання 14: Виявлення помилок «sudo потребує tty»
cr0x@server:~$ tail -n 10 /home/cr0x/cron-logs/report.log
sudo: a terminal is required to read the password; either use the -S option to read from standard input or configure an askpass helper
sudo: a password is required
Що це означає: У cron немає інтерактивного TTY, тому запити пароля не проходять.
Рішення: Не використовуйте інтерактивний sudo у cron. Використайте запис у crontab від root, обмежене правило у sudoers для конкретної команди без пароля або переробіть задачу так, щоб не потребувала підвищення привілеїв.
Завдання 15: Переконайтеся в дозволах файлів та членстві в групах під час виконання
cr0x@server:~$ id cr0x
uid=1000(cr0x) gid=1000(cr0x) groups=1000(cr0x),27(sudo),113(lxd)
cr0x@server:~$ ls -l /data/reports
drwxr-x--- 2 root analytics 4096 Dec 29 08:00 /data/reports
Що це означає: cr0x не належить до групи analytics, тому запис у /data/reports буде неможливим.
Рішення: Виправте членство в групі та дозволи належним чином або записуйте в місце, доступне користувачу задачі.
Завдання 16: Перевірте, чи не блокує користувача allow/deny для cron
cr0x@server:~$ sudo ls -l /etc/cron.allow /etc/cron.deny
ls: cannot access '/etc/cron.allow': No such file or directory
-rw-r--r-- 1 root root 0 Apr 5 2024 /etc/cron.deny
Що це означає: Якщо /etc/cron.deny існує і містить ім’я користувача, cron відмовить у його crontab.
Рішення: Переконайтеся, що користувач дозволений. У багатьох середовищах це не використовується, але коли використовується — це тихий підступ.
Підводні камені PATH і середовища (і виправлення)
Якщо ваша задача працює вручну, але не за розкладом, починайте з припущення про PATH/середовище. Це найчастіша причина, і її зазвичай легко підтвердити.
Cron не читає ваші dotfiles
Ваш інтерактивний shell може встановлювати PATH у ~/.profile, ~/.bashrc або в корпоративному «ініціалізаторі шеллу», який підвантажує середовища мов. Cron не джерелить ці файли. Це не грубість; це детермінізм.
Виправлення: Покладіть потрібні змінні середовища або:
- на початок crontab (краще, коли вам потрібні різні налаштування для різних задач), або
- всередину скрипту (краще, коли ви хочете, щоб скрипт був переносним поза cron).
Задайте PATH явно (і тримайте його нудним)
Більшість продакшн-крон-завдань повинні встановлювати відомий PATH і потім викликати бінарники з абсолютними шляхами для критичних дій. Так, обидва підходи. Це не надмірність; це захист від майбутніх переміщень пакетів, змін адміністраторів і «дружніх» скриптів ініціалізації.
Рекомендований PATH:
/usr/local/sbin:/usr/local/binдля локально встановлених інструментів адміністрування/usr/sbin:/usr/bin:/sbin:/binдля системних пакетів
Якщо ви покладаєтеся на ~/.local/bin, додайте його явно. Але запитайте себе, чому продакшн-крон залежить від per-user pip-встановлень. Це не заборонено; просто крихке.
Сюрпризи локалі та кодування
В інтерактивних сесіях у вас може бути багата локаль; у cron ви можете отримати LANG=C або C.UTF-8. Якщо ваш скрипт парсить дати, сортує рядки або обробляє не-ASCII текст — це має значення.
Виправлення: Встановіть LANG і LC_ALL явно у скрипті. Якщо ви працюєте з UTF-8 — вирішіть це заздалегідь.
Змінні середовища, які є вручну, але відсутні в cron
Типові приклади:
SSH_AUTH_SOCK(ваш SSH-агент): є в терміналі; відсутній у cron.AWS_PROFILE/AWS_REGION: в shell; відсутні у cron.PYTHONPATH/ активація virtualenv: є вручну; немає у cron.HTTP_PROXY/NO_PROXY: налаштовані на десктопі; відсутні на серверах.
Виправлення: Інжектуйте потрібні змінні в рядок crontab або в сам скрипт. Краще: не покладайтеся на стан агента або інтерактивну автентифікацію для запланованих задач; використовуйте виділені облікові дані з мінімальними правами.
Жарт №1: Cron не «забуває» ваш PATH. Він просто ніколи його не вивчив.
Робочий каталог, відносні шляхи та umask
Відносні шляхи — зручність, доки вони не стають стратегією в продакшні. Cron робить їх болючими, бо не гарантує робочий каталог, що відповідає вашим очікуванням у терміналі.
Завжди використовуйте абсолютні шляхи в cron-завданнях
Якщо ваш скрипт виконує ./bin/tool, ../data або записує в logs/output.log, це фактично лотерея: іноді працює (коли ви тестуєте з кореня скрипту), іноді — падає (коли cron запускає його звідкись іншого).
Варіанти виправлення:
- Використовуйте абсолютні шляхи скрізь.
- Або на початку скрипту зробіть
cdу відомий каталог (cd /opt/app || exit 1).
Різниця umask змінює дозволи файлів
Ваш інтерактивний shell може встановлювати umask, який дає файлам групозаписувані права; cron може використовувати інший. Симптоми тонкі: файли створюються, але інші процеси не можуть їх читати, або наступна задача падає.
Виправлення: Задайте umask явно у скрипті. Вирішіть, які дозволи вам справді потрібні, замість того щоб спадкувати «відчуття».
HOME і розширення тильди
Cron зазвичай задає HOME, але не покладайтеся на це. Також використання ~ у записах crontab може поводитися по-різному залежно від шеллу і цитування. Ваш скрипт не повинен покладатися на тильду для знаходження критичних файлів.
Виправлення: Використовуйте /home/username (або краще: виділений каталог додатку) у cron. Нудність перемагає.
Дозволи, користувачі, групи та sudo у cron
Найшвидший спосіб змусити cron-завдання «працювати» — запустити його як root. Найшвидший спосіб отримати інцидент пізніше — залишити так надалі.
Знайте, від кого виконується задача
Є кілька місць для визначення cron-завдань:
- Користувацький crontab через
crontab -e: виконується від імені цього користувача. - Root crontab через
sudo crontab -e: виконується від root. - /etc/crontab і /etc/cron.d/*: містять явне поле користувача.
- /etc/cron.hourly, daily, weekly, monthly: скрипти запускаються як root, але середовище виконання відрізняється (часто через
run-parts).
Не використовуйте sudo у cron, якщо ви не продумали це
sudo у cron падає з передбачуваних причин: немає TTY, запити пароля, скидання середовища та політики. Якщо вам справді потрібні привілеї, віддайте перевагу:
- запису в crontab від root, який викликає скрипт з обмеженими дозволами, або
- мінімальному правилу у
sudoers, що дозволяє конкретну команду без пароля, з явними шляхами і без підстановок.
Трюк з підстановками у sudoers рано чи пізно буде використаний як сходинка. Вам може не сподобатися, хто по ній підійметься.
Членство в групах і додаткові групи
Коли cron працює від імені користувача, він використовує членство цієї особи в групах. Якщо ви додали користувача в групу недавно, вам може знадобитися нова сесія входу, щоб ваші мануальні тести відповідали реальності; cron міг уже використовувати оновлений список груп, або ваш shell — ні. Ця невідповідність створює плутанину «в мене працює».
Виправлення: Перевіряйте доступ через sudo -u user і явно перевіряйте дозволи. Не робіть припущень.
Різниця шелів: /bin/sh vs bash, і чому це важливо
Багато випадків «cron загадково падає» насправді — «скрипт використовує синтаксис bash, а cron запускає його під sh/dash». Помилки можуть бути тихими, якщо ви ніколи не зберігаєте stderr.
Поширені bash-особливості, що ломаються під sh
[[ ... ]]замість[ ... ]- масиви
sourceзамість.- підстановка процесу:
<( ... ) - відмінності в поведінці
set -o pipefail
Виправлення: Пишіть POSIX-совісні shell-скрипти і запускайте їх під /bin/sh, або явно вказуйте bash і пишіть під bash. Комбінація «трохи того й цього» призведе до викликів о 3:00 ранку.
Примусове встановлення шелла у crontab (коли доречно)
Додайте на початок crontab:
SHELL=/bin/bash
Але на цьому не зупиняйтесь. Все одно використовуйте правильний shebang у скрипті. Скрипт має запускатися поза cron, і інтерпретатор повинен бути явним.
Логування: куди йде вивід cron на Ubuntu 24.04
Cron має два способи повідомити, що сталося: журнал про виконання та вивід самої задачі. Люди плутають їх і звинувачують cron у «мовчанні». Cron не мовчить; ви просто не підключили мікрофон.
Журнали виконання: journald і/або syslog
На Ubuntu 24.04 journalctl -u cron часто — найшвидше джерело правди: він показує, що cron запустив команду і від імені якого користувача. Він не покаже вивід вашого скрипту, якщо ви його не перенаправите.
Вивід задачі: пошта (якщо є MTA) або ваші перенаправлення
Традиційно cron надсилає stdout/stderr користувачу поштою (або на MAILTO). Але багато серверів не мають встановленого MTA. У такому випадку ви побачите повідомлення «No MTA installed, discarding output». Це сигнал: перенаправте вивід самі.
Моя сильна думка: продакшн-крон-завдання завжди повинні перенаправляти вивід у лог-файл або в syslog/journald явно. Пошта — гарний друг, але не основний канал.
Віддавайте перевагу структурованим логам, коли можете
Якщо ваша задача важлива (пайплайни даних, бекапи, експорт білінгу), робіть краще, ніж просто скидати текст. Випускайте позначки часу, коди виходу і однорядкові підсумки. Якщо можете — відправляйте логи у централізовану систему. Але навіть локальний лог з ротацією — величезний прогрес у порівнянні з «думаю, вона запускалася».
Cron vs systemd timers: коли переходити
Cron досі надійний робітник. Але на Ubuntu 24.04 systemd timers часто краще підходять, коли вам потрібен порядок залежностей, ізоляція та уніфіковане логування.
Залишайтеся з cron коли
- Завдання просте і локальне.
- Потрібна сумісність зі старою практикою і існуючим флотом.
- Вже налаштоване хороше логування і виявлення помилок.
Переважно systemd timers коли
- Ви хочете логи в journald автоматично.
- Потрібні повторні спроби, залежності (network-online) або контроль ресурсів.
- Потрібен явний unit, який ви можете перевірити через
systemctl statusі моніторити.
Корисна ментальна модель: cron — «вистрілив і забув». systemd timers — «оголосіть намір і спостерігайте стан». В продуктивному середовищі спостережуваність зазвичай перемагає.
Мінімальний приклад патерну таймера (концептуально)
Навіть якщо ви залишаєтеся з cron сьогодні, вивчіть підхід з таймерами. Це страховка, коли проблеми середовища cron перетворюються на рутину. І так, ви все ще можете запускати той самий скрипт; просто отримуєте кращі інструменти навколо нього.
Три корпоративні міні-історії з продакшну
Міні-історія 1: Інцидент через хибне припущення
Команда, близька до фінансів, мала нічну експортну задачу: зібрати числа, відформатувати CSV і покласти його у папку SFTP. Розробник запускав її вручну — все працювало. У staging теж. У продакшні вона «випадково» падала двічі на тиждень.
Хибне припущення було невелике: скрипт використовував ~/exports і очікував, що робочий каталог — корінь репозиторію. Коли хтось тестував вручну — вони завжди запускали з репозиторію і в інтерактивному shell, який налаштовував PATH і кілька змінних.
У cron задача запускалася з PWD=/home/user і SHELL=/bin/sh. Розширення тильди поводилося по-різному залежно від цитування, і відносний шлях потрапляв у неіснуючу директорію. Скрипт падав рано, писав помилку в stderr, а cron намагався її надіслати поштою. MTA не було. Отже помилка була відкинута. Єдиний «сигнал» — відсутні файли, помічені людьми вранці.
Виправлення були нудними і рішучими: абсолютні шляхи, явний PATH і локаль, перенаправлення виводу у лог. Потім додали простий маркер «останній успішний запуск» і алерт, якщо він не оновлений до 02:00. Після цього задача або успішно виконувалася, або падала голосно — а це й було головною метою.
Міні-історія 2: Оптимізація, що відбилася боком
Інфраструктурна група хотіла пришвидшити задачу обслуговування, що видаляла старі артефакти. Вони замінили уважний скрипт на швидкий one-liner з find через xargs -P для паралельних видалень. Виглядало добре в швидкому тесті. Також скоротило час виконання з хвилин до секунд у невеликому каталозі.
У продакшні проявилися передбачувані режимі відмов: пробіли та newlines у іменах файлів. Паралельний pipeline іноді цільовував неправильний шлях. Зазвичай це було безпечним. Але одного разу він видалив набір «поточних» артефактів, бо логіка відбору зіпсувалася.
Ще один бік ефект — конкуренція за ресурси. Паралельні видалення спричинили сплески метаданихових операцій, які підскочили latency I/O для інших робочих навантажень. Ніщо не впало, але це викликало таймаути і повторні спроби в залежному сервісі саме в ту хвилину, коли запускався prune. Класичне «оптимізували одне й заплатили десь інше».
Вони відкотилися до повільнішого, але безпечного підходу: find -print0 з null-дельмітерами, без небезпечного парсингу, і навмисне обмеження швидкості. Це не було гламурно. Воно перестало ламати продакшн. Після інциденту висновок був простий: якщо cron-завдання видаляє щось, ставте «швидкість» як вимогу, яку треба заслужити, а не як налаштування за замовчуванням.
Міні-історія 3: Нудна, але правильна практика, що врятувала день
Команда платформи даних щодня виконувала ETL імпорт через cron. Була одна нецікава, але важлива практика: кожен запуск писав timestamped лог і фінальний однорядковий підсумок з часом виконання, кількістю записів і кодом виходу. Також записувався маркер .ok лише після повного успіху всіх кроків. І скрипт запускався з set -euo pipefail у bash з обережною обробкою винятків.
Одного ранку дашборди втратили свіжі дані. Люди одразу підозріли кластер сховища. Але логи cron розповіли історію менше ніж за хвилину: задача стартувала, розв’язала DNS, і впала на конкретному API виклику з чіткою помилкою про TLS валідацію. Код виходу був ненульовий, маркер не оновлений. Ніякої таємниці.
Причина виявилася у зміні сертифікату на upstream і застарілому CA-бандлі в контейнерному образі, який використовував скрипт. Команда оновила бандл, перезапустила задачу, і маркер став зеленим. Інцидент був локалізований, бо задача була спостережувана і детерміністична.
Не було героїчного порятунку. Просто команда поставилася до cron як до продакшну, а не як до магічного календаря з вайбом.
Поширені помилки: симптом → причина → виправлення
Цей розділ захочеться тримати відкритим під час сесії відладки. Шаблони повторюються в компаніях, командах і на всіх рівнях досвіду.
1) Симптом: «В терміналі працює, але cron не створює файли»
Причина: Відносні шляхи або несподіваний робочий каталог. Скрипт пише в ./output, а cron запускається з HOME або /.
Виправлення: Використовуйте абсолютні шляхи або cd на початку скрипту. Додайте логування з pwd і ls очікуваних директорій.
2) Симптом: «Журнал cron показує CMD запущено, але нічого не відбувається.»
Причина: Вивід був відкинутий, бо немає MTA, або ви ніколи не перенаправляли вивід.
Виправлення: Перенаправляйте stdout/stderr у лог-файл. Якщо хочете пошту — встановіть/налаштуйте MTA, але не покладайтеся на нього як на єдиний канал.
3) Симптом: «/bin/sh: 1: source: not found» або «[[: not found»
Причина: Скрипт використовує можливості bash, а виконується під /bin/sh (dash).
Виправлення: Додайте shebang для bash і/або встановіть SHELL=/bin/bash у crontab. Або перепишіть скрипт під POSIX sh.
4) Симптом: «command not found» для інструментів, які точно встановлені
Причина: PATH у cron мінімальний; інтерактивний PATH ширший.
Виправлення: Задайте PATH явно. Для критичних команд використовуйте абсолютні шляхи.
5) Симптом: задача працює, але поводиться інакше (сортування, парсинг, regex)
Причина: Відмінності локалі (LANG/LC_ALL) між інтерактивом і cron.
Виправлення: Встановіть локаль явно у скрипті. Не парсьте людський вивід; використовуйте машиночитні формати де можливо.
6) Симптом: «Permission denied» тільки під cron
Причина: Інший користувач, відсутнє членство в групі, різниця umask або запис у кореневі директорії типу /var/log.
Виправлення: Переконайтеся в власності/дозволах і членстві в групах. Записуйте логи у користувацькі шляхи або створіть виділену директорію з контрольованими дозволами.
7) Симптом: sudo не працює в cron
Причина: sudo запитує пароль; cron немає TTY. Або політика sudo забороняє.
Виправлення: Не використовуйте інтерактивний sudo. Запускайте від потрібного користувача (root, якщо потрібно) або створіть мінімальні записи у sudoers для конкретних команд.
8) Симптом: задача «не запускається» під час переходу на DST
Причина: DST може пропускати або дублювати локальні часи; задачі біля 02:00 можуть бути пропущені або повторені.
Виправлення: Тримайте сервери в UTC. Якщо мусите використовувати локальний час — уникайте крихких часових вікон і робіть задачу ідемпотентною.
Жарт №2: Перехід на літній/зимовий час: бо іноді ваша cron-задача заслуговує впасти двічі через ту саму помилку.
Чеклісти / покроковий план
Чекліст A: Зробити cron-завдання production-grade за 15 хвилин
- Використовуйте абсолютні шляхи для скрипту і всіх файлів, які читаються/пишуться.
- Задайте інтерпретатор явно у скрипті:
#!/bin/bash(або#!/usr/bin/python3, тощо). - Задайте PATH або в crontab, або на початку скрипту до відомого значення.
- Задайте локаль якщо парсите/сортуєте текст:
LC_ALL=C.UTF-8або інша свідома опція. - Логуйте stdout/stderr з урахуванням ротації. Почніть з
~/cron-logs, якщо немає стратегії логування. - Виходьте з ненульовим кодом при помилці і не ховайте помилки за
|| true, якщо ви їх не обробляєте свідомо. - Додайте маркер успіху (файл або метрика), щоб сповіщати про простій запуску.
Чекліст B: Відлагодити невдалу задачу без здогадок
- Підтвердіть виконання cron:
journalctl -u cron. - Перенаправте вивід і відтворіть помилку у лог-файлі.
- Збережіть середовище від cron (
env | sort) і порівняйте з інтерактивним (env | sort). - Запустіть задачу в очищеному середовищі через
/usr/bin/env -i. - Виправте PATH, shell, дозволи, робочий каталог у цьому порядку.
- Лише потім відлагоджуйте логіку додатку.
Чекліст C: Визначити, чи переходити на systemd timer
- Якщо потрібні порядок залежностей (мережа, точки монтування) — віддайте перевагу systemd.
- Якщо потрібне консистентне логування і прості перевірки статусу — віддайте systemd.
- Якщо потрібне «запустити пропущені задачі після простою» — розгляньте anacron або systemd з persistent timers.
- Якщо завдання просте і стабільне — cron підходить; просто зміцніть середовище.
Поширені питання
1) Чому моя cron-задача працює, коли я вставляю її в термінал?
Тому що ваша термінальна сесія має багатше середовище: PATH, локаль, агенти автентифікації і конфігурацію dotfiles. Cron запускається з мінімальним середовищем і неінтерактивним шеллом. Робіть залежності явними.
2) Де дивитися журнали cron на Ubuntu 24.04?
Почніть з journalctl -u cron. Залежно від конфігурації логування, cron також може писати в /var/log/syslog. Вивід вашого скрипту там з’явиться тільки якщо ви його перенаправите.
3) Чому я бачу «No MTA installed, discarding output»?
Cron намагався надіслати stdout/stderr вашої задачі поштою, але MTA не встановлено/не налаштовано. Перенаправте вивід у файл (або встановіть MTA, якщо пошта — частина вашої операційної моделі).
4) Де краще задавати PATH — у crontab чи у скрипті?
Якщо кілька cron-завдань використовують спільне середовище, зручно встановити PATH на початку crontab. Якщо скрипт може запускатися поза cron (CI, systemd, вручну), задайте PATH всередині скрипту також. В продакшні я часто роблю обидва і все одно використовую абсолютні шляхи для критичних бінарів.
5) У моєму скрипті масиви bash. Як змусити cron використовувати bash?
Використайте shebang bash (#!/bin/bash) і/або встановіть SHELL=/bin/bash у crontab. Потім захоплюйте stderr, щоб бачити синтаксичні помилки, якщо щось ще не так.
6) Чому cron не має доступу до мережевих ресурсів, які доступні вручну?
Типові причини: відсутні proxy-змінні, відсутні облікові дані, DNS не готовий під час завантаження, або задача запускається до того, як доступні мережеві монтовані шляхи. Cron не виконує порядок залежностей; systemd timers роблять це.
7) Як протестувати точно те, що зробить cron, не чекаючи?
Використайте відтворення в очищеному середовищі: /usr/bin/env -i з мінімальним набором змінних і запустіть той самий інтерпретатор, що і cron. Це швидко виявляє проблеми з PATH і локаллю.
8) Чи безпечно писати логи в /var/log з користувацького crontab?
Не за замовчуванням. /var/log зазвичай належить root. Логінг у користувацьку директорію або створення виділеної директорії з контрольованими дозволами і правилами logrotate — правильний шлях. Уникайте логів з глобальними правами запису.
9) Моя задача іноді запускається двічі. Cron це дублює?
Cron сам по собі навмисно не дублює один запис, але ви могли визначити задачу в двох місцях (користувацький crontab і /etc/cron.d, наприклад). DST також може дублювати локальні часи. Пошукайте конфігурації cron і тримайте сервери в UTC.
10) Коли варто припинити боротися з cron і перейти на systemd timer?
Коли потрібні порядок залежностей, консистентне логування, зручні перевірки статусу, контроль ресурсів або семантика «запустити після простою». Cron підходить для простих періодичних задач; systemd краще для production-розкладу з хорошою спостережуваністю.
Висновок: практичні наступні кроки
Якщо ваша cron-задача на Ubuntu 24.04 працює вручну, але не за розкладом, не починайте з переписування скрипту. Почніть з доведення того, що cron зробив, збережіть середовище і зробіть виконання детерміністичним: абсолютні шляхи, явний PATH, явний shell, явне логування.
Потім зробіть її спостережуваною: тримайте лог, записуйте коди виходу і додавайте маркер успіху або алерт. Надійність — це передусім прибирати містичність, а не додавати вигадки.
Одна думка, яку варто тримати на стікері: «Hope is not a strategy.»
— Gene Kranz