Cron в Ubuntu: настройка заданий по расписанию Перейти к содержимому
+7 (920) 556-26-91 пн–пт: 10:00–19:00 (МСК)
Проконсультироваться Обсудить задачу

Инструкции

Cron в Ubuntu: как настроить задания по расписанию

Формат записи cron, управление заданиями, почему команды не работают из-за окружения, правильный скрипт с журналированием и таймеры systemd.

Cron в Ubuntu: как настроить задания по расписанию

Короткий ответ: cron запускает команды по расписанию, и запись задания состоит из пяти полей времени и самой команды. Главные ошибки — не указан абсолютный путь к программе, не задано окружение, и вывод команды никуда не пишется, из-за чего о сбое никто не узнаёт. Надёжнее запускать через cron не длинную команду, а скрипт с ключом set -euo pipefail и журналированием.

Этот материал продолжает цикл про настройку Ubuntu 24.04 на VPS.

Формат записи

минута час день_месяца месяц день_недели команда
  0-59  0-23   1-31       1-12    0-7 (0 и 7 — воскресенье)
Запись Когда выполняется
0 3 * * * Каждый день в 03:00
*/15 * * * * Каждые 15 минут
0 */6 * * * Каждые 6 часов
30 2 * * 1 По понедельникам в 02:30
0 4 1 * * Первого числа каждого месяца в 04:00
0 9-18 * * 1-5 Каждый час с 9 до 18 по рабочим дням

Символ */15 означает «каждые 15 единиц», 1-5 — диапазон, 1,15 — перечисление. Символ * — любое значение.

Управление заданиями

crontab -l              # показать задания текущего пользователя
crontab -e              # редактировать
crontab -r              # удалить ВСЕ задания (без подтверждения!)
crontab -u www-data -l  # задания другого пользователя (нужен root)

Команда crontab -r удаляет весь список без вопроса о подтверждении. Перед правками полезно сохранить вывод crontab -l в файл.

Системные задания лежат отдельно:

Путь Назначение
/etc/crontab Системный файл, содержит поле пользователя
/etc/cron.d/ Файлы от пакетов, тоже с полем пользователя
/etc/cron.daily/ Скрипты ежедневного запуска
/etc/cron.hourly/ Скрипты ежечасного запуска
/var/spool/cron/crontabs/ Файлы пользовательских заданий

В файлах /etc/crontab и /etc/cron.d/* есть дополнительное поле — пользователь, от имени которого выполняется команда. В пользовательских заданиях этого поля нет.

Окружение — главный источник проблем

Cron запускает команды с минимальным окружением: другой PATH, нет переменных из профиля оболочки. Поэтому команда, которая работает в терминале, может не работать в cron.

PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
SHELL=/bin/bash
MAILTO=admin@example.com

0 3 * * * /usr/bin/php /var/www/app/artisan schedule:run >> /var/log/cron-app.log 2>&1
Строка Зачем
PATH Чтобы находились программы без полного пути
SHELL Явно указать оболочку
MAILTO Куда отправлять вывод при сбое

Надёжнее всё же указывать полный путь к программе: /usr/bin/php, /usr/bin/python3, /usr/local/bin/backup.sh. Узнать путь: which php.

Журналирование — обязательная часть

0 3 * * * /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1

Конструкция >> файл 2>&1 направляет в лог и обычный вывод, и ошибки. Без неё вывод уходит в почту, которая на сервере обычно не настроена, — и о падении задания никто не узнает.

Чтобы лог не рос бесконечно, добавляют ротацию. Файл /etc/logrotate.d/cron-app:

/var/log/backup.log {
    weekly
    rotate 8
    compress
    missingok
    notifempty
}

Правильный скрипт для cron

#!/usr/bin/env bash
set -euo pipefail

exec 2>&1

echo "[$(date '+%F %T')] старт"

cd /var/www/app || exit 1
/usr/bin/php artisan queue:work --stop-when-empty

echo "[$(date '+%F %T')] готово"
Строка Смысл
set -euo pipefail Остановиться при ошибке, неопределённой переменной или сбое в конвейере
exec 2>&1 Ошибки идут туда же, куда обычный вывод
cd /var/www/app Cron запускает команду из домашнего каталога, а не из каталога проекта
Отметки времени По логу видно, когда задание началось и когда закончилось

Без set -e ошибка на середине скрипта не остановит выполнение, и скрипт завершится с кодом успеха, не сделав работу. Это самая коварная ошибка в заданиях по расписанию.

Что можно и чего нельзя

Cron подходит для регулярных задач: копирование, очистка, отправка отчётов, обработка очереди. Не подходит для задач, которые должны выполняться строго один раз в определённый момент, и для длительных процессов, которые могут пересечься сами с собой.

Защита от наложения запусков:

*/10 * * * * /usr/bin/flock -n /tmp/task.lock /usr/local/bin/task.sh

Команда flock -n не даст запустить второй экземпляр, пока работает первый. Без этой защиты долгая задача, запускаемая каждые 10 минут, накопит параллельные процессы и исчерпает ресурсы сервера.

systemd-таймеры как альтернатива

В современных системах расписание можно задавать через таймеры systemd. Их преимущества: журналирование через journalctl автоматически, зависимость от других служб, запуск пропущенного задания после включения сервера.

# /etc/systemd/system/backup.timer
[Unit]
Description=Ежедневное резервное копирование

[Timer]
OnCalendar=daily
Persistent=true

[Install]
WantedBy=timers.target
systemctl enable --now backup.timer
systemctl list-timers

Параметр Persistent=true — то, чего нет у cron: если сервер был выключен в момент запуска, задание выполнится после включения.

Диагностика

Команда Что показывает
journalctl -u cron --since today Что запускал cron
grep CRON /var/log/syslog Записи о запуске заданий
crontab -l Текущий список заданий
systemctl status cron Работает ли сама служба

Задание, которое не запускается вовсе, обычно означает, что служба cron остановлена или в записи опечатка в поле времени. Задание, которое запускается и не работает, почти всегда связано с окружением или путями.