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. Он закрывает все места, где
обычно что-то ломается молча:
- задача Scheduler упала или не запустилась —
pingSteadrun(); - перестал запускаться сам
schedule:run— одна переменная в.env; - воркер очереди умер — heartbeat воркера;
- очередь отстала или воркер слушает не ту очередь — сквозная проверка;
- job упал — алерт с текстом исключения.
composer require steadrun/laravel-monitor
Schedule::command('backup:run')->dailyAt('03:00')->pingSteadrun('ВАШ-UUID');
Алерты приходят в Telegram или на почту. Бесплатно можно следить за пятью задачами. Для PHP без фреймворка есть готовая функция пинга.
Что дальше
- Laravel: всё о пакете
steadrun/laravel-monitor. - Crontab и расписание cron: синтаксис и частые ошибки.
- Как проверить, что cron выполняется: если задача не запускается.