SSD Nodes Learn Hosting plans →
Руководства Matt ConnorАвтор: Matt Connor · Обновлено 2026-09-04

Почему не выполняется задача cron: 5 частых причин

Узнайте, почему ваша задача cron не запускается. Разбираем пять критических ошибок: пустой PATH, неэкранированный знак процента, неверный crontab, потерянный вывод и shell.

Почему ваша задача cron не выполняется

Задача cron, которая «никогда не выполняется», почти всегда запускается. Она выполняется в окружении, отличном от вашей оболочки, завершается с ошибкой в первую же секунду, а сообщение об ошибке отправляется туда, где вы его не читаете. Пять причин объясняют почти каждый случай: путь поиска (search path), знак процента, неверный файл crontab, вывод, направленный в почту, и скрипт, ожидающий сеанс входа в систему.

cron — это демон (фоновая служба), который считывает файлы crontab и запускает команды по расписанию. Он не читает ваш .bashrc, не открывает терминал, не запускает оболочку входа (login shell) и не уведомляет вас о сбоях команд. Каждая причина, описанная ниже, вытекает из этих четырех фактов.

Разберите их по порядку, начав с вопроса, лежащего в основе всего: сработал ли cron вообще? «cron не запустил задачу» и «задача запустилась и завершилась» — это разные проблемы, у которых нет ничего общего, поэтому сначала ответьте на этот вопрос.

Сработал ли cron?

Имя юнита демона различается в зависимости от семейства дистрибутивов. Проверьте оба варианта, а затем изучите лог.

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

В Debian и Ubuntu юнит называется cron. В Fedora, Rocky и Alma он называется crond. На конкретной машине существует только одно из этих имен, поэтому сообщение об ошибке для одного из них — это нормальное поведение, а не неисправность.

Изучите записи, созданные вашей системой. Не ищите строку, скопированную из руководства, так как формулировки различаются в зависимости от реализации cron и настроек логирования. Вам нужно проверить только два момента: есть ли запись в ту минуту, которая указана в вашем расписании, и содержит ли эта запись вашу команду. Если запись с вашей командой есть, значит, cron выполнил свою часть работы, а ошибка кроется внутри самой команды. Отсутствие записи означает, что cron не получил ваше расписание, что соответствует причине 3 ниже.

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

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

Если ни юнита, ни лога не существует, возможно, 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. Задание завершается ошибкой на первой же строке, и оболочка выводит сообщение о том, что команда не найдена; точная формулировка зависит от используемой оболочки.

Найдите полный путь к каждой команде, которую использует ваше задание.

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 никогда не читает этот файл. Вызывайте версию бинарного файла по абсолютному пути или подключайте (source) инициализирующий скрипт менеджера первой строкой вашего собственного скрипта.

Причина 2: знак процента завершает команду

В поле команды crontab символ % не является обычным символом. Первый неэкранированный % завершает команду. Всё, что следует за ним, передается команде через стандартный ввод, а каждый последующий % превращается в символ новой строки. Это штатная функция cron для передачи коротких данных в программу, и именно поэтому запись с датой в имени файла — классический пример нерабочей строки crontab.

Напишите 0 3 * * * /usr/bin/tar -czf /srv/backups/site-$(date +%F).tar.gz /srv/site, и tar никогда не увидит отформатированную дату. cron обрезает строку на первом %, поэтому оболочка получает незавершенную подстановку команды, а остальная часть строки поступает как стандартный ввод. Экранируйте каждый знак процента обратной косой чертой.

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

Два уровня обработки читают эту строку по очереди. \% — это правило cron, применяемое до запуска чего-либо. $(date +\%F) — это подстановка команды, применяемая позже оболочкой, которую запускает cron. Понимание того, какой уровень отвечает за конкретный символ, — это и есть основной секрет.

Более безопасная практика — полностью вынести логику из crontab. Поместите её в скрипт, где знак процента не имеет специального значения.

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

Тогда строка crontab будет содержать только путь и перенаправление. Crontab, который можно прочитать с первого взгляда, — это crontab, который легко отлаживать.

Причина 3: какой именно crontab вы редактировали?

Единого файла crontab не существует. Есть несколько файлов с разными владельцами и разным количеством полей; задача, записанная не в тот файл, будет невидимой.

  • crontab -e редактирует crontab пользователя, который выполняет команду. sudo crontab -e редактирует crontab пользователя root. Два администратора, отлаживающие один и тот же сервер, часто просматривают разные файлы.
  • sudo crontab -l -u deploy выводит crontab другого пользователя; это способ проверить, что именно установлено для учетной записи, которая должна выполнять задачу.
  • /etc/crontab и каждый файл в /etc/cron.d содержат одно дополнительное поле между расписанием и командой: имя пользователя, от которого нужно запустить задачу. Если вставить строку из пяти полей в /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 проверяет синтаксис файла перед установкой. После сохранения прочитайте вывод команды. Если она отклоняет файл, предыдущая версия остается активной, а ваши изменения не вступают в силу — это выглядит так, будто cron вас игнорирует.

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

Причина 4: вывод отправляется на почту, которую никто не читает

cron собирает всё, что задача записывает в стандартный поток вывода (stdout) и стандартный поток ошибок (stderr). Если задача вывела хоть что-то, 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, стандартный поток ошибок сохранит свое исходное назначение, и ошибка, которую вы ищете, будет именно той частью, которая никогда не попадет в файл.

Журнал (journal) — еще одна подходящая цель. 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 (терминал). Команда, запрашивающая подтверждение, открывающая редактор или отображающая индикатор выполнения, может зависнуть или завершиться с ошибкой. Добавьте флаг для неинтерактивного режима, если инструмент его поддерживает.
  • Отсутствует SSH-агент. SSH_AUTH_SOCK не передается в окружение cron, поэтому команды ssh или rsync, которые работали благодаря загруженному агенту, теперь не проходят аутентификацию. Создайте для задачи отдельный ключ, владельцем которого является пользователь, выполняющий задачу.
  • Отсутствует шина пользовательской сессии, поэтому systemctl --user из cron-задачи завершается ошибкой, пока не установлена переменная XDG_RUNTIME_DIR. Использование системного юнита — более правильное решение.

В Fedora, Rocky и Alma есть еще один подозреваемый. SELinux ограничивает cron-задачи, поэтому при попытке доступа к пути с неожиданной меткой в доступе будет отказано, даже если права на файл выглядят корректно. Проверьте наличие отказов с помощью 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 хорош в одном: запуск команды в заданное время. В остальном он слаб. Таймер предоставляет логи в journal без перенаправления вывода, код завершения, который можно запросить позже, порядок запуска относительно network-online.target и случайную задержку, чтобы сотни серверов не начинали выполнение в одну и ту же секунду. Если вашей задаче требуется что-то из этого, настройка systemd service и timer на VPS потребует меньше усилий, чем поддержка строки в crontab. Создание сервисного файла ставит перед вами вопрос, который никогда не возникает в cron: как юнит понимает, что работа действительно началась. Поэтому сначала изучите что означает Type= для юнитов типов simple, forking и notify, так как скрипт, который переходит в фоновый режим при типе по умолчанию, оставляет юнит в состоянии active, хотя процесс завершился. Поведение при повторных попытках также настраивается здесь, поскольку политики перезапуска systemd определяют действия после сбоя, на что cron вообще не дает ответа.

Оставьте cron для простых задач. Перенесите всё, что имеет зависимости или политику повторных попыток, на таймеры. Оба инструмента могут работать на одном сервере, поэтому миграцию не обязательно завершать за один раз.

FAQ

Почему моя задача cron работает при ручном запуске, но не работает из cron?

Потому что окружение вашей оболочки и окружение cron различаются. Ваша интерактивная оболочка считывает /etc/profile и ~/.bashrc, которые задают PATH, локаль и переменные агента. cron запускает команду без этих настроек, из другого рабочего каталога и иногда с использованием другой оболочки. Используйте абсолютные пути для всех команд, задавайте необходимые переменные в начале файла crontab или внутри самого скрипта. Запланируйте тестовую задачу, которая раз в минуту записывает вывод env | sort, pwd и id в лог-файл: это позволит увидеть реальное окружение cron, вместо того чтобы пытаться угадать его.

Как проверить, действительно ли cron выполнил мою задачу?

Изучите лог демона. Используйте 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 почтовый агент (MTA) не установлен, поэтому сообщения отбрасываются, и кажется, что задача ничего не выводит. Перенаправляйте вывод в файл с помощью >> /path/to/log 2>&1, соблюдая порядок, чтобы стандартный поток ошибок следовал за стандартным выводом, или передавайте его через logger -t myjob и читайте с помощью journalctl -t myjob. Не используйте > /dev/null 2>&1, пока продолжаете отладку.

Что лучше использовать: cron или systemd timer?

Используйте cron для простых команд, запускаемых в фиксированное время, особенно если задачу может потребоваться перенести на систему без systemd. Используйте таймер, если вам нужно видеть вывод в журнале без перенаправлений, иметь возможность запросить статус завершения, настроить запуск после поднятия сети, добавить случайную задержку старта или политику повторных попыток при сбоях. Оба инструмента могут работать на одном сервере, поэтому вы можете переносить задачи на таймеры постепенно, по мере необходимости.