PHPFlowPHP, MySQL, Laravel и JavaScript

Cron в Linux: синтаксис расписания, crontab -e и примеры

Обновлено:

Коротко

Cron в Linux запускает команды и скрипты по расписанию. Задания записываются в crontab командой crontab -e, по одному на строку: пять полей времени (минута, час, день месяца, месяц, день недели) и команда. Например, строка */5 * * * * /usr/bin/php /var/www/site/cron.php выполняет PHP-скрипт каждые 5 минут. Ниже синтаксис cron с примерами, запуск PHP и где смотреть логи, если задание не срабатывает.

Содержание
  1. Как устроен cron
  2. Синтаксис расписания cron
  3. Примеры расписания cron
  4. Как редактировать crontab
  5. Запуск PHP-скрипта по cron
  6. Логи cron: как узнать, запускалось ли задание
  7. Почему cron не запускает задание
  8. Cron или таймеры systemd

Как устроен cron

Cron это служба (демон), которая раз в минуту просыпается, читает таблицы заданий и запускает те, чьё время наступило. Своя таблица (crontab) есть у каждого пользователя, плюс системные файлы. В Ubuntu и Debian служба называется cron, в CentOS, Rocky Linux, AlmaLinux и Fedora она называется crond (пакет cronie), в Astra Linux используется тот же cron, что и в Debian.

Схема работы cron
Как работает cron: каждую минуту служба проверяет таблицы и запускает задания, чьё время пришло

Где лежат задания:

МестоЧьи заданияОсобенность
crontab -eТекущего пользователяХранится в /var/spool/cron/crontabs/, руками не править
/etc/crontabСистемныеШестое поле: от какого пользователя запускать
/etc/cron.d/Задания пакетов и ваши файлыФормат как у /etc/crontab
/etc/cron.daily/, .hourly, .weeklyСкрипты без расписанияЗапускаются раз в день, час, неделю

Проверить, что служба работает, можно командой systemctl status cron (на RHEL-системах systemctl status crond). Строка "active (running)" значит, что всё в порядке.

Синтаксис расписания cron

Каждая строка crontab состоит из пяти полей времени и команды. Поля разделяются пробелами:

┌──────── минута (0-59)
│ ┌────── час (0-23)
│ │ ┌──── день месяца (1-31)
│ │ │ ┌── месяц (1-12 или jan-dec)
│ │ │ │ ┌ день недели (0-7, где 0 и 7 это воскресенье, или sun-sat)
│ │ │ │ │
* * * * * команда

Специальные символы в полях:

  • * любое значение: "каждую минуту", "каждый час".
  • , список: 1,15 в поле дня означает 1-е и 15-е число.
  • - диапазон: 1-5 в поле дня недели означает с понедельника по пятницу.
  • / шаг: */10 в поле минут означает каждые 10 минут, 0-30/5 каждые 5 минут в первые полчаса.

Если заданы и день месяца, и день недели (оба не *), cron срабатывает, когда совпадает любое из них. Строка 0 9 1 * 1 выполнится 1-го числа и ещё каждый понедельник, а не только в понедельник, выпавший на 1-е.

Сокращения @reboot, @daily и другие

Вместо пяти полей можно написать одно слово:

ЗаписьТо же самоеКогда
@rebootнет аналогаОдин раз после загрузки системы
@hourly0 * * * *Каждый час в 00 минут
@daily0 0 * * *Каждый день в полночь
@weekly0 0 * * 0По воскресеньям в полночь
@monthly0 0 1 * *1-го числа в полночь

Примеры расписания cron

Готовые cron-выражения для частых задач. Подставьте свою команду вместо команда.

ВыражениеКогда выполняется
* * * * *Каждую минуту
*/5 * * * *Каждые 5 минут
*/10 * * * *Каждые 10 минут
0 * * * *Каждый час, в начале часа
30 */2 * * *Каждые 2 часа в 30 минут
0 3 * * *Ежедневно в 03:00
0 1 * * *Ежедневно в 01:00 (в выдаче часто как "cron 0 1")
0 9-18 * * 1-5Каждый час с 9 до 18 по будням
0 0 * * 0Раз в неделю, в воскресенье в полночь
0 2 1 * *1-го числа каждого месяца в 02:00
0 0 1 1 *Раз в год, 1 января

Cron не умеет запускать задания чаще раза в минуту. Если нужен запуск каждые 30 секунд, поставьте две строки, во второй добавьте задержку: * * * * * sleep 30; команда. Для таких частот лучше подходит постоянно работающий процесс (воркер очереди) или таймер systemd.

Проверить выражение перед сохранением удобно в онлайн-сервисе crontab.guru: он переводит строку в человеческий текст и показывает ближайшие запуски.

Как редактировать crontab

Своё расписание пользователь редактирует командой crontab -e. При первом запуске система спросит, каким редактором пользоваться; новичкам проще выбрать nano.

  1. Подключитесь к серверу по SSH под тем пользователем, от чьего имени должен работать скрипт.
  2. Выполните crontab -e.
  3. Если появился выбор редактора, введите номер пункта с /bin/nano и нажмите Enter.
  4. Перейдите в конец файла и допишите строку задания.
  5. Сохраните файл в nano сочетанием Ctrl + O, затем Enter.
  6. Выйдите из nano сочетанием Ctrl + X. Сообщение "crontab: installing new crontab" значит, что расписание принято.
Выбор редактора при первом запуске crontab -e
Первый запуск crontab -e: система предлагает выбрать редактор, проще всего nano

Если открылся vim, нажмите I для режима ввода, допишите строку, затем Esc, наберите :wq и нажмите Enter. Выйти без сохранения: :q!. Сменить редактор навсегда можно командой select-editor или переменной EDITOR=nano crontab -e.

Строка задания в crontab
Задание в редакторе nano: пять полей времени, затем команда с полными путями

Другие команды crontab

crontab -l                 # показать свои задания
crontab -l > ~/cron.bak    # сохранить копию расписания в файл
crontab ~/cron.bak         # загрузить расписание из файла (заменит текущее)
sudo crontab -e            # задания пользователя root
sudo crontab -u www-data -e   # задания другого пользователя
crontab -r                 # удалить ВСЁ расписание без подтверждения

Клавиши e и r стоят рядом. crontab -r удаляет все задания сразу и без вопросов, поэтому держите копию из crontab -l. Перезапускать cron после crontab -e не нужно: служба сама замечает изменения. Перезапуск (sudo systemctl restart cron) имеет смысл только после смены часового пояса или правки настроек самой службы.

Запуск PHP-скрипта по cron

Самая частая задача веб-разработчика: выполнять PHP-скрипт по расписанию (рассылка, очистка старых записей, выгрузка товаров). В cron пишут полный путь к интерпретатору и к файлу, потому что окружение у cron минимальное и папки из вашего PATH он может не знать.

# Узнать путь к PHP
which php            # /usr/bin/php
php -v               # версия CLI

# Если на сервере несколько версий, указывайте нужную явно
ls /usr/bin/php*     # /usr/bin/php8.3  /usr/bin/php8.4 ...

Строка в crontab для запуска скрипта каждые 15 минут с записью вывода в лог:

*/15 * * * * cd /var/www/site && /usr/bin/php8.4 cron/cleanup.php >> /var/log/site-cron.log 2>&1

Здесь cd переходит в папку сайта, чтобы относительные пути в скрипте работали так же, как при ручном запуске. >> файл дописывает вывод в лог, а 2>&1 отправляет туда же ошибки. Папка лога должна быть доступна на запись пользователю, от которого запускается задание.

Пример скрипта для cron

Скрипт удаляет просроченные токены сброса пароля из базы. Он не даёт запустить себя из браузера и пишет итог в стандартный вывод, который попадёт в лог:

<?php
declare(strict_types=1);

// Запуск только из командной строки
if (PHP_SAPI !== 'cli') {
    http_response_code(403);
    exit('Forbidden');
}

date_default_timezone_set('Europe/Moscow');

$pdo = new PDO(
    'mysql:host=localhost;dbname=shop;charset=utf8mb4',
    'shop_cron',
    getenv('DB_PASSWORD') ?: '',
    [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
);

$stmt = $pdo->prepare('DELETE FROM password_resets WHERE created_at < :limit');
$stmt->execute(['limit' => date('Y-m-d H:i:s', strtotime('-1 day'))]);

echo date('Y-m-d H:i:s'), ' удалено записей: ', $stmt->rowCount(), PHP_EOL;

Подключение к базе здесь сделано через подготовленный запрос, подробнее о нём в статье про PDO в PHP. Учтите, что у консольного PHP может быть свой php.ini, отдельный от того, что использует сайт: лимиты памяти, часовой пояс и включённые расширения бывают разными. Как найти нужный файл, рассказано в статье где находится php.ini, а работа с датами и часовыми поясами в скриптах разобрана в материале про дату и время в PHP.

Защита от двойного запуска

Если скрипт иногда работает дольше интервала, следующий запуск начнётся поверх предыдущего. Утилита flock не даст запустить вторую копию, пока работает первая:

*/5 * * * * flock -n /tmp/export.lock /usr/bin/php /var/www/site/cron/export.php >> /var/log/export.log 2>&1

Laravel и WordPress

В Laravel достаточно одной строки cron, а само расписание описывается в коде (routes/console.php):

* * * * * cd /var/www/app && php artisan schedule:run >> /dev/null 2>&1

В WordPress встроенный wp-cron.php запускается при заходах посетителей: на сайте без трафика задачи не выполняются, а на нагруженном лишний раз тратят ресурсы. Отключите его строкой define('DISABLE_WP_CRON', true); в wp-config.php и вызывайте из системного cron:

*/10 * * * * cd /var/www/wp && /usr/bin/php wp-cron.php > /dev/null 2>&1

Cron на виртуальном хостинге

На хостинге без SSH задания добавляют в панели управления, в разделе с названием вроде "Планировщик заданий" или "CRON": выбираете время из списков или вводите выражение, а в поле команды указываете путь к PHP нужной версии из подсказки хостинга и полный путь к скрипту от корня аккаунта. Если панель позволяет только открыть URL, используйте curl -s https://site.ru/cron.php?key=..., а в скрипте проверяйте длинный секретный ключ, чтобы задание не мог запустить посторонний.

Логи cron: как узнать, запускалось ли задание

Сам факт запуска cron записывает в системный журнал. Вывод команды туда не попадает, его нужно перенаправлять в свой файл, как в примерах выше.

# Ubuntu и Debian
grep CRON /var/log/syslog | tail -20

# Через journald (любой дистрибутив с systemd)
journalctl -u cron --since "1 hour ago"     # в RHEL-системах -u crond

# CentOS, Rocky, AlmaLinux
sudo tail -f /var/log/cron
Проверка логов cron в Ubuntu
В системном журнале видно, что задание запустилось, а результат работы скрипта записан в его лог

Если вывод команды не перенаправлен, cron пытается отправить его письмом владельцу задания. Переменная MAILTO в начале crontab задаёт адрес; строка MAILTO="" отключает письма полностью. Без настроенной почты на сервере письма просто теряются, поэтому надёжнее писать в файл.

Почему cron не запускает задание

ПричинаКак исправить
Команда не найдена: в cron короткий PATHПисать полные пути: /usr/bin/php, /usr/local/bin/composer
Скрипт работает вручную, но не в cronДобавить cd в папку проекта, задать переменные окружения в crontab
Символ % в команде обрезает строкуЭкранировать: date +\%Y-\%m-\%d
Нет перевода строки после последнего заданияОставить пустую строку в конце crontab
Нет прав на файл или логЗапускать от владельца сайта, а не от root, проверить права папки лога
Время запуска "не то"Проверить часовой пояс сервера командой timedatectl
Файл в /etc/cron.d/ игнорируетсяУбрать точку и расширение из имени файла, указать пользователя шестым полем

Чтобы воспроизвести окружение cron при отладке, запустите команду с почти пустым набором переменных: env -i /bin/sh -c 'cd /var/www/site && /usr/bin/php cron/cleanup.php'. Если в таком виде скрипт падает, причина в окружении. Ошибки самого PHP удобнее видеть, если в консольном скрипте включён вывод ошибок; как это сделать, показано в статье про вывод ошибок PHP.

Переменные окружения в crontab

В начале crontab можно задать переменные, которые получат все задания ниже:

SHELL=/bin/bash
PATH=/usr/local/bin:/usr/bin:/bin
MAILTO=""
DB_PASSWORD=секретный_пароль

0 4 * * * /usr/bin/php /var/www/site/cron/report.php >> /var/log/report.log 2>&1

Подстановка вида $HOME/bin в этих строках не работает: значения берутся как есть.

Cron или таймеры systemd

На современных дистрибутивах наряду с cron есть таймеры systemd. Они удобнее, когда нужно видеть каждое выполнение в journalctl, догонять пропущенные запуски после выключения сервера (Persistent=true) или ограничивать память и процессор задания. Для обычных задач сайта cron проще: одна строка, и всё работает. Если проект живёт в Docker, задания либо запускают с хоста через docker compose exec, либо выносят в отдельный контейнер; варианты есть в статье про Docker для PHP.

Частые вопросы

Как запустить задание cron каждую минуту?

Запишите в crontab строку * * * * * команда. Пять звёздочек значат "каждую минуту, каждого часа, каждого дня".

Как настроить cron каждые 5 минут?

Используйте шаг в поле минут: */5 * * * * команда. Так же записываются каждые 10 (*/10) и 15 минут (*/15).

Как сохранить изменения после crontab -e?

В nano нажмите Ctrl + O, Enter и Ctrl + X, в vim наберите :wq. Сообщение "installing new crontab" подтверждает сохранение.

Где лежит файл crontab?

Таблицы пользователей хранятся в /var/spool/cron/crontabs/ (в RHEL-системах /var/spool/cron/), но править их нужно только через crontab -e. Системные задания лежат в /etc/crontab и /etc/cron.d/.

Где смотреть логи cron?

В Ubuntu выполните grep CRON /var/log/syslog или journalctl -u cron, в CentOS и AlmaLinux смотрите /var/log/cron. Вывод самой команды сохраняйте в свой файл через >> файл 2>&1.

Нужно ли перезапускать cron после изменения crontab?

Нет, служба сама подхватывает изменения из crontab -e. Перезапуск командой sudo systemctl restart cron нужен только после смены часового пояса или настроек службы.