SSD Nodes Learn Hosting plans →
Посібники Matt ConnorВід Matt Connor · Оновлено 2026-09-04

Чому cron job не запускається: 5 причин

Дізнайтеся про 5 причин збою cron job: мінімальний PATH, неекранований %, неправильний crontab, втрачений email-вивід і залежність від shell.

Чому cron job не запускається

Завдання cron, яке «ніколи не запускається», майже завжди вже запускалося. Воно виконувалося в середовищі, відмінному від вашої оболонки, завершилося з помилкою в першу секунду, а повідомлення потрапило не туди, де ви його шукаєте. Майже кожен такий випадок пояснюється однією з п’яти причин: шляхом пошуку, символом відсотка, неправильним файлом crontab, виводом, надісланим електронною поштою, або скриптом, якому потрібен сеанс входу в систему.

cron — це daemon (фоновий сервіс), який читає файли crontab і запускає команди за розкладом. Він не читає вашу .bashrc, не відкриває термінал, не запускає login shell і не повідомляє, коли команда завершується з помилкою. Усі наведені нижче причини випливають із цих чотирьох фактів.

Розгляньте їх по черзі. Спочатку дайте відповідь на запитання, спільне для всіх цих випадків: чи cron взагалі запустив завдання? Твердження «cron ніколи не запускав завдання» і «завдання запустилося та завершилося» описують різні проблеми, які не мають нічого спільного. Спочатку з’ясуйте саме це.

Чи cron взагалі запустився?

У різних сімействах дистрибутивів daemon має різні назви unit. Перевірте обидві, а потім прочитайте журнал.

systemctl status cron
systemctl status crond
journalctl -u cron --since "2 hours ago"
journalctl -u crond --since "2 hours ago"

У Debian і Ubuntu unit називається cron. У Fedora, Rocky та Alma він називається crond. На конкретній машині існує лише одна з цих назв, тому повідомлення про невідомий unit в одній із двох команд є нормальним і не свідчить про несправність.

Прочитайте записи, які створила саме ваша система. Не шукайте рядок, скопійований із посібника, оскільки формулювання відрізняються між реалізаціями cron і конфігураціями журналювання. Потрібно перевірити лише дві речі: чи є запис на хвилину, указаною у вашому розкладі, і чи містить цей запис назву вашої команди. Запис із назвою вашої команди означає, що cron виконав свою частину роботи, а помилка міститься всередині команди. Повна відсутність запису означає, що cron не отримав ваш розклад. Це причина 3 нижче.

Деякі образи передають повідомлення cron через rsyslog у файл, а не до журналу. Перевірте /var/log на наявність файлу з назвою, пов’язаною з cron або syslog, а потім прочитайте його кінець.

ls -l /var/log
sudo tail -n 50 /var/log/syslog

Якщо немає ні unit, ні журналу, можливо, cron просто не встановлено. Мінімальні хмарні образи та контейнери часто не містять його.

dpkg -l cron
rpm -q cronie
sudo apt install cron
sudo dnf install cronie
sudo systemctl enable --now cron

Причина 1: cron не має вашого PATH

Інтерактивна оболонка формує PATH зі значень /etc/profile, ~/.profile, ~/.bashrc і всього, що підключають ці файли. Для завдання cron нічого з цього не виконується. cron запускає команду у власному скороченому оточенні, тому програму поза стандартними системними каталогами не знайдено. Це може бути будь-що з /usr/local/bin, /opt, менеджера версій мови програмування, віртуального середовища Python або робочого простору Go. Завдання завершується з помилкою на першому рядку, а оболонка записує повідомлення на кшталт "not found". Точне формулювання залежить від оболонки, яка його виконувала.

Знайдіть фактичний шлях до кожної команди, яку використовує завдання.

command -v docker
command -v node
readlink -f "$(command -v node)"

Після цього або вкажіть у завданні абсолютні шляхи, або один раз задайте PATH на початку crontab.

PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
0 3 * * * /usr/local/bin/mytool run

Отримайте цей список на власній машині за допомогою echo "$PATH" і вилучіть усе, що існує лише в інтерактивному сеансі. Тут важливе одне правило: cron не розгортає змінні в таких рядках присвоєння. PATH=$PATH:/usr/local/bin зберігає буквальний текст $PATH:/usr/local/bin, тому завдання отримує шлях пошуку, який не містить жодного придатного каталогу. Вкажіть увесь список явно.

Для менеджера версій одного шляху недостатньо. nvm, pyenv, rbenv і asdf встановлюють функцію оболонки або каталог shims із вашого .bashrc, а завдання cron ніколи не читає цей файл. Викликайте бінарний файл потрібної версії за абсолютним шляхом або підключіть init-скрипт менеджера першим рядком власного скрипту.

Причина 2: знак відсотка завершує команду

У полі команди crontab % не є звичайним символом. Перше неекрановане % завершує команду. Усе, що йде після нього, передається команді через стандартне введення, а кожне наступне % перетворюється на символ нового рядка. Це справжня функція cron для передавання коротких вхідних даних програмі. Вона також пояснює, чому ім’я файлу з датою є типовою причиною помилки в записі crontab.

Запишіть 0 3 * * * /usr/bin/tar -czf /srv/backups/site-$(date +%F).tar.gz /srv/site, і tar не отримає відформатовану дату. cron обріже рядок на першому %, тому shell отримає незавершену підстановку команди, а решта рядка надійде через стандартне введення. Екрануйте кожен знак відсотка зворотною косою рискою.

0 3 * * * /usr/bin/tar -czf /srv/backups/site-$(date +\%F).tar.gz /srv/site

Один рядок читають два рівні, послідовно. \% — це правило cron, яке cron застосовує до запуску будь-яких процесів. $(date +\%F) — це підстановка команди, яку пізніше застосовує shell, запущений cron. Суть полягає в тому, щоб розуміти, якому рівню належить кожен символ.

Надійніше взагалі не розміщувати логіку в crontab. Винесіть її у скрипт, де знак відсотка не має спеціального значення.

#!/bin/bash
set -euo pipefail
stamp="$(date +%F)"
tar -czf "/srv/backups/site-${stamp}.tar.gz" /srv/site

У рядку crontab тоді залишаються лише шлях і перенаправлення. Такий рядок crontab легко прочитати з першого погляду, а отже, його легше налагоджувати.

Причина 3: який crontab ви редагували?

Є не один crontab. Існує кілька файлів із різними власниками та різною кількістю полів. Завдання, записане не в той файл, не буде видно.

  • crontab -e редагує crontab користувача, від імені якого виконано команду. sudo crontab -e редагує crontab root. Під час налагодження на одному сервері двоє людей часто переглядають два різні файли.
  • sudo crontab -l -u deploy показує crontab іншого користувача. Так можна перевірити, що саме встановлено для облікового запису, від імені якого має запускатися завдання.
  • /etc/crontab і кожен файл у /etc/cron.d містять одне додаткове поле між розкладом і командою: користувача, від імені якого потрібно виконати команду. Якщо вставити рядок із п’ятьма полями з crontab користувача у /etc/cron.d, перше слово команди буде прочитано як ім’я користувача.
  • Файли в /etc/cron.d мають називатися лише з використанням літер, цифр, символів підкреслення та дефісів. Файл із назвою backup.sh або site.conf буде пропущено лише через неправильну назву. Перейменуйте його на backup і знову перевірте журнал.
  • Власником файлів у /etc/cron.d має бути root. Вони також не повинні бути доступними для запису групі або іншим користувачам. ls -l /etc/cron.d одразу показує обидва ці факти.
  • Скрипти, розміщені в /etc/cron.daily та споріднених каталогах, мають відповідати тому самому правилу іменування та мати біт виконання. Якщо біта виконання немає, скрипт буде мовчки пропущено.
  • /etc/cron.allow і /etc/cron.deny визначають, кому взагалі дозволено встановлювати crontab. Якщо один із цих файлів існує на вашому сервері, прочитайте його, перш ніж припускати, що вашому користувачу дозволено мати crontab.

Встановлюйте crontab користувача командою crontab, а не редагуйте файл spool вручну, оскільки crontab перевіряє файл перед встановленням. Після збереження прочитайте виведення команди. Якщо команда відхиляє файл, попередня версія залишається активною, а ваша зміна не застосовується. Це виглядає так само, ніби cron вас ігнорує.

Власник також визначає права доступу. Завдання у crontab root створює файли, власником яких є root, тому застосунок, що читає ці файли, може не мати права записувати їх. Завдання у crontab звичайного користувача не може читати каталог, доступний лише root. Власник має відповідати характеру роботи: обслуговування застосунку слід виконувати від імені облікового запису самого застосунку. Саме на цьому ґрунтується заміна WordPress wp-cron системним завданням cron. Режим доступу до файлів, які створює завдання, визначається успадкованим umask. Це значення також може відрізнятися від umask вашої оболонки, тому варто прочитати як umask визначає права доступу до файлів, якщо результати завдання створюються недоступними для читання.

Причина 4: вивід надходив у пошту, яку ніхто не читає

cron збирає все, що завдання записує у стандартний вивід і стандартний потік помилок. Якщо завдання записало хоча б щось, cron передає цей текст локальній поштовій системі. Повідомлення надходить власнику crontab або адресату, якого визначає MAILTO. На мінімально налаштованому VPS зазвичай немає MTA (mail transfer agent), тому доставити повідомлення нікому. Помилка існувала лише мить, а потім зникла без сліду. Саме тому зламане завдання виглядає тихим.

Спрямуйте вивід у файл, яким ви керуєте.

0 3 * * * /usr/local/sbin/backup-site.sh >> /var/log/backup-site.log 2>&1

>> додає стандартний вивід у файл. 2>&1 спрямовує стандартний потік помилок туди, куди в цей момент спрямований стандартний вивід, тому цей параметр має стояти після перенаправлення. Якщо записати їх у зворотному порядку, як у 2>&1 >> file, стандартний потік помилок збереже початкове призначення, і помилка, яку ви шукаєте, не потрапить у файл.

Журнал — ще одне зручне місце для виводу. logger записує повідомлення в syslog із вибраним вами тегом.

0 3 * * * /usr/local/sbin/backup-site.sh 2>&1 | logger -t backup-site

Перегляньте їх за допомогою journalctl -t backup-site. Так власний вивід завдання зберігається поруч із записами cron, і часову послідовність легко відстежувати. Якщо вам також потрібно фіксувати, хто саме запускав кожну команду на сервері, це окрема система. Її описано в матеріалі аудит команд користувачів на сервері.

MAILTO="" на початку crontab вимикає пошту для наведених нижче завдань. Встановлення MAILTO на реальну адресу допоможе лише за наявності справного MTA, тому спочатку переконайтеся, що пошта справді залишає сервер.

Під час налагодження діє одне правило: ніколи не додавайте > /dev/null 2>&1. Це найпоширеніший рядок у кожному crontab, і він видаляє єдиний наявний доказ. Поверніть його пізніше, коли завдання запрацює.

Причина 5: скрипт очікує змінні середовища, яких cron йому не передає

Коли команду знайдено, а її вивід захоплено, залишається все інше, що ваша сесія надає автоматично.

  • Оболонка може бути не bash. Перевірте це за допомогою ls -l /bin/sh. У Debian і Ubuntu він вказує на dash, тому перевірка з подвійними квадратними дужками, масиви та source завершуються синтаксичною помилкою. Додайте до скрипту рядок #!/bin/bash і запускайте скрипт через нього або задайте SHELL на початку crontab.
  • Робочий каталог не збігається з каталогом, у якому ви перебували. Використовуйте абсолютні шляхи всюди або виконайте cd до потрібного каталогу в першому рядку скрипту. Відносний шлях є найпоширенішою окремою причиною, через яку завдання «працює під час ручного запуску».
  • Локаль відрізняється від локалі вашої сесії. Будь-яка команда, яка форматує дату або число чи сортує текст, може повертати інший результат за іншого значення LANG. Якщо наступний крок аналізує цей результат, задайте локаль у скрипті, а не покладайтеся на поточне середовище.
  • TTY (термінала) немає. Команда, яка запитує підтвердження, відкриває редактор або відображає індикатор виконання, може зависнути або завершитися. Додайте відповідний non-interactive прапорець, якщо інструмент його підтримує.
  • SSH agent відсутній. SSH_AUTH_SOCK немає в середовищі cron, тому команда ssh або rsync, яка працювала завдяки завантаженому agent, тепер не проходить автентифікацію. Надайте завданню окремий ключ, власником якого є користувач цього завдання.
  • Шини сеансу користувача немає, тому systemctl --user із cron job завершується помилкою, доки не буде задано XDG_RUNTIME_DIR. Кращим рішенням буде system unit.

У Fedora, Rocky та Alma є ще одна можлива причина. SELinux обмежує cron jobs, тому доступ до шляху з неочікуваною міткою забороняється, навіть якщо права доступу до файлу виглядають правильними. Перевірте заборони за допомогою sudo ausearch -m avc -ts recent і прочитайте Основи SELinux для сервера, перш ніж щось вимикати.

Однохвилинна перевірка, яка показує середовище cron

Не припускайте, які змінні доступні в середовищі cron, а прочитайте їх. Створіть скрипт, який виводить усі змінні, заплануйте його запуск щохвилини, зачекайте, а потім прочитайте файл.

cat > /home/deploy/cron-probe.sh <<'EOF'
#!/bin/bash
echo "=== probe ==="
date -Is
pwd
id
echo "SHELL=$SHELL"
echo "LANG=$LANG"
command -v node || echo "node is not on this PATH"
env | sort
EOF
chmod +x /home/deploy/cron-probe.sh

Додайте один рядок до crontab користувача, від імені якого запускається потрібне завдання. Використайте абсолютні шляхи з обох боків.

* * * * * /home/deploy/cron-probe.sh >> /home/deploy/cron-probe.log 2>&1

Зачекайте хвилину, потім прочитайте /home/deploy/cron-probe.log і порівняйте його з результатами виконання тих самих команд у власній оболонці. Рядок PATH, робочий каталог і локаль зазвичай самі пояснюють причину помилки. Зверніть увагу на дві деталі налаштування: знаки відсотка містяться всередині скрипту, де правило cron на них не поширюється, а шлях до журналу доступний для запису користувачу, від імені якого запускається завдання.

Видаліть цей рядок із crontab одразу після отримання відповіді. Завдання, яке запускається щохвилини та дописує дані у файл, заповнить невеликий диск, причому зробить це безшумно.

Чи вказано саме той розклад, який вам потрібен?

Рядок у crontab користувача починається з п’яти полів: хвилина, година, день місяця, місяць, день тижня. Два з них взаємодіють у спосіб, який часто спричиняє непорозуміння.

Якщо поля дня місяця та дня тижня обмежені, тобто жодне з них не дорівнює *, cron запускає завдання, коли збігається будь-яке з цих полів. 0 0 13 * 5 означає не «п’ятниця, 13-те». Завдання запускається опівночі 13-го числа кожного місяця та опівночі щоп’ятниці. Щоб вказати лише один конкретний день, залиште одне з двох полів як *, а інше перевіряйте всередині скрипту.

cron використовує системний часовий пояс. Багато образів VPS налаштовані на UTC (координований всесвітній час), тому завдання, заплановане на 03:00, запускається о 03:00 UTC, що у вашому часовому поясі може припадати на середину дня. timedatectl виводить часовий пояс, який фактично використовує ваш сервер. Перевірте його, а не припускайте, що він збігається з часовим поясом вашого ноутбука.

Варто знати ще про дві пастки розкладу. @reboot спрацьовує під час запуску самого cron. Це не обов’язково збігається з моментом готовності мережі, тому завдання, якому потрібні DNS або віддалений хост, може завершитися помилкою під час завантаження системи, а під час кожного наступного ручного запуску виконуватися успішно. Крім того, ніщо не заважає повільному завданню запуститися знову, поки попередній процес ще виконується. Обгорніть його блокуванням.

*/5 * * * * /usr/bin/flock -n /tmp/backup-site.lock /usr/local/sbin/backup-site.sh >> /var/log/backup-site.log 2>&1

flock -n негайно завершує виконання, якщо блокування вже встановлене, тому паралельний запуск припиняється, а не запускається одночасно з першим.

Коли systemd timer є кращим інструментом

cron добре виконує одне завдання: запускає цю команду в цей час. В інших випадках він має обмеження. timer записує журнал без перенаправлення виводу, дає змогу пізніше перевірити код завершення, налаштувати порядок запуску відносно network-online.target і додати випадкову затримку, щоб сотні серверів не запускали завдання в одну й ту саму секунду. Якщо завданню потрібна будь-яка з цих можливостей, systemd service і timer на VPS налаштувати простіше, ніж обґрунтовувати використання рядка в crontab. Під час написання service виникає запитання, якого cron ніколи не ставить: як unit визначає, що робота справді розпочалася. Тому спочатку прочитайте що означає Type= для simple, forking і notify units, оскільки скрипт, який сам переходить у фоновий режим за типом, встановленим за замовчуванням, залишає unit в активному стані, хоча за ним більше нічого не працює. Поведінку під час повторних спроб також потрібно визначати в unit, оскільки політики перезапуску systemd визначають, що відбувається після збою, а cron взагалі не має відповіді на це запитання.

Залишайте cron для невеликих завдань. Усе, що має залежності або політику повторних спроб, переносьте до timer. Обидва інструменти можуть працювати на одному сервері, тому не потрібно завершувати таку міграцію за один підхід.

FAQ

Чому моє завдання cron працює вручну, але завершується помилкою з cron?

Тому що середовища вашої оболонки та cron відрізняються. Оболонка входу читає /etc/profile і ~/.bashrc, які встановлюють PATH, локаль і змінні вашого агента. cron запускає команду без цього, з іншого робочого каталогу, а іноді й з іншою оболонкою. Використовуйте абсолютні шляхи для кожної команди, задайте потрібні змінні на початку crontab або всередині скрипту та заплануйте пробне завдання на одну хвилину, яке записує env | sort, pwd і id у файл журналу. Так ви зможете побачити фактичне середовище cron, а не робити припущення.

Як перевірити, чи cron справді запустив моє завдання?

Перегляньте журнал daemon. Використовуйте journalctl -u cron у Debian та Ubuntu або journalctl -u crond у Fedora, Rocky та Alma. У деяких образах повідомлення натомість передаються через rsyslog у файл у каталозі /var/log. Знайдіть запис за хвилину, яку вказує розклад, і перевірте, чи містить він вашу команду. Відсутність запису означає, що cron не отримав цей розклад, тому переконайтеся, що ви відредагували правильний crontab. Запис без результату означає, що команду запущено, але вона завершилася, тому перенаправте її вивід.

Чому date +%Y не працює всередині crontab?

cron обробляє % як спеціальний символ у полі команди. Перший неекранований % завершує команду, усе після нього передається цій команді як стандартний ввід, а кожен наступний % перетворюється на символ нового рядка. Тому ім’я файлу з датою у форматі не передається програмі, для якої ви його створили. Екрануйте кожен символ відсотка як \% або перенесіть команду в скрипт і викликайте скрипт із cron, оскільки всередині скрипту символ відсотка не має спеціального значення.

Куди надходить вивід мого завдання cron?

У локальну поштову систему, на адресу власника crontab або на адресу, яку вказує MAILTO. У більшості образів VPS не встановлено mail transfer agent, тому повідомлення відкидається, і завдання виглядає таким, що не повертає виводу. Перенаправте вивід у файл за допомогою >> /path/to/log 2>&1, зберігаючи саме такий порядок, щоб стандартна помилка надходила за стандартним виводом, або передайте його через logger -t myjob і прочитайте за допомогою journalctl -t myjob. Не використовуйте > /dev/null 2>&1, доки триває налагодження.

Що вибрати: cron чи таймер systemd?

Використовуйте cron для простої команди, яку потрібно запускати у фіксований час, особливо якщо її доведеться перенести на машину без systemd. Використовуйте таймер, якщо потрібен вивід у журналі без перенаправлення, доступний для запиту код завершення, запуск після готовності мережі, випадкова затримка запуску або політика повторної спроби після помилки. Обидва варіанти можуть працювати на одному сервері, тому завдання можна переносити по одному, коли це стане доцільно.