STEADRUN

Cron в PHP и Laravel: запуск и мониторинг

Обновлено 02.10.2026

PHP-скрипт запускается по cron так же, как любая другая команда. Но у PHP есть свои ловушки. В консоли может оказаться другая версия PHP и другой php.ini, а относительные пути перестают работать. А в Laravel cron нужен всего один, остальное делает Scheduler. Разберём оба случая.

PHP-скрипт по cron

Строка crontab для PHP-скрипта:

*/15 * * * * /usr/bin/php /var/www/site/cron/import.php >> /var/www/site/logs/import.log 2>&1

Что здесь важно:

  • Полный путь к PHP. У cron короткий PATH, и просто php может не найтись. Путь покажет which php.
  • Та же версия PHP, что у сайта. На сервере бывает установлено несколько версий. Сайт может работать на PHP 8.3, а /usr/bin/php указывать на 8.1. Проверьте php -v и при необходимости укажите путь к нужной версии, например /usr/bin/php8.3.
  • Вывод в лог. Без >> … 2>&1 ошибки скрипта пропадут.

CLI и веб — разные настройки

Из cron PHP работает в режиме CLI, и у него свой php.ini. Посмотреть, какой именно, можно командой php --ini. Расширения, лимит памяти и часовой пояс в CLI могут отличаться от того, что у сайта. Ограничения времени выполнения в CLI по умолчанию нет, поэтому зависший скрипт будет висеть сколько угодно.

Относительные пути

Cron запускает скрипт из домашней папки пользователя, а не из папки скрипта. Если скрипт подключает файлы как include 'config.php', он их не найдёт. Пишите пути от папки скрипта:

require __DIR__.'/config.php';

Запуск через URL — плохая идея

Иногда задачу запускают так: * * * * * wget -q -O - https://site.ru/cron.php. Это работает, но:

  • запрос упирается в таймауты веб-сервера и PHP-FPM, и долгая задача обрывается на середине;
  • адрес открыт всему интернету, и задачу может запустить кто угодно;
  • ошибка PHP превращается в ответ 500, который никто не читает.

Если есть доступ к консоли сервера, запускайте скрипт через php напрямую.

Чтобы задачи не наслаивались

Если импорт иногда идёт дольше 15 минут, следующий запуск начнётся поверх предыдущего. Запретить это можно через flock. Пока блокировка занята, новый запуск сразу завершится.

*/15 * * * * /usr/bin/flock -n /tmp/import.lock /usr/bin/php /var/www/site/cron/import.php

Laravel Scheduler

В Laravel не нужно заводить строку crontab на каждую задачу. Достаточно одной, которая раз в минуту запускает Scheduler, а он уже решает, какие задачи пора выполнить:

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

Сами задачи описываются в коде. В Laravel 11 и новее — в routes/console.php:

use Illuminate\Support\Facades\Schedule;

Schedule::command('backup:run')->dailyAt('03:00');
Schedule::command('orders:export')->everyFifteenMinutes()->withoutOverlapping();

В Laravel 10 то же самое пишется в методе schedule() файла app/Console/Kernel.php.

Полезные команды:

Команда Что делает
php artisan schedule:list Показывает задачи и время следующего запуска
php artisan schedule:test Запускает выбранную задачу сейчас
php artisan schedule:work Запускает Scheduler в терминале, удобно на локальной машине

Несколько вещей, о которых стоит знать:

  • withoutOverlapping() не даёт задаче запуститься, пока не закончился прошлый запуск.
  • onOneServer() нужен, если schedule:run работает на нескольких серверах. Без него задача выполнится на каждом. Для этого нужен общий кэш, например Redis.
  • Часовой пояс. Scheduler считает время в часовом поясе приложения, timezone в config/app.php. По умолчанию это UTC, и dailyAt('03:00') выполнится в 06:00 по Москве. Часовой пояс можно задать и для отдельной задачи: ->timezone('Europe/Moscow').

Очереди Laravel

Воркер очереди (php artisan queue:work) работает постоянно, поэтому запускать его через cron не нужно. Частая ошибка — строка * * * * * php artisan queue:work в crontab. Через несколько часов на сервере окажутся сотни воркеров. Воркеры держит запущенными Supervisor:

[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/app/artisan queue:work --sleep=3 --tries=3 --max-time=3600
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
user=deploy
numprocs=2
redirect_stderr=true
stdout_logfile=/var/www/app/storage/logs/worker.log
stopwaitsecs=3600

После каждого деплоя перезапускайте воркеры командой php artisan queue:restart, иначе они продолжат работать со старым кодом.

Как узнать, что задачи выполняются

Cron и Scheduler не сообщают о сбоях. Если задача упала, Scheduler запишет это в лог, а если перестал запускаться он сам, не запишет ничего. С очередями так же. Упавший воркер не оставляет следов, задания просто копятся.

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

Schedule::command('backup:run')->dailyAt('03:00')
    ->pingBefore('https://example.com/start')
    ->thenPing('https://example.com/done');

Но кто-то должен этих запросов ждать и сообщать, если они не пришли. Для этого есть пакет steadrun/laravel-monitor. Он закрывает все места, где обычно что-то ломается молча:

composer require steadrun/laravel-monitor
Schedule::command('backup:run')->dailyAt('03:00')->pingSteadrun('ВАШ-UUID');

Алерты приходят в Telegram или на почту. Бесплатно можно следить за пятью задачами. Для PHP без фреймворка есть готовая функция пинга.

Что дальше