Чим PHP CLI відрізняється від PHP-FPM для завдань cron
PHP-FPM обслуговує HTTP-запити через Nginx і працює в режимі пулу воркерів зі спільним php.ini та лімітами, заданими для вебнавантаження. PHP CLI — це окремий бінарник /usr/bin/php8.3, який запускається з командного рядка або з crontab і читає свій власний конфіг, зазвичай із /etc/php/8.3/cli/php.ini. Значення memory_limit, max_execution_time і error_reporting у CLI та FPM можуть відрізнятися, і це часта причина, чому скрипт у браузері працює, а той самий скрипт за розкладом падає або навпаки.
Друга відмінність — оточення. У процесу cron немає змінних PATH, HOME і локалі, які є в інтерактивній оболонці, і немає робочого каталогу за замовчуванням: він завжди стартує з домашнього каталогу користувача, від імені якого запущено cron.
Як правильно прописати завдання у crontab
Правило crontab повинно вказувати повний шлях до бінарника PHP, повний шлях до скрипту і робочий каталог, якщо скрипт використовує відносні шляхи до файлів.
*/5 * * * * cd /var/www/app && /usr/bin/php8.3 artisan schedule:run >> /var/log/app/cron.log 2>&1
Символ && гарантує, що php запуститься лише після успішного переходу в каталог, а 2>&1 перенаправляє помилки в той самий лог-файл, що і звичайний вивід. Без цього фатальні помилки PHP тихо зникають: cron надсилає їх на локальну пошту, яку ніхто не читає.
Власна версія php.ini для CLI
Перевірити, який конфіг насправді використовує CLI, можна однією командою — вона покаже шлях до файлу, а не припущення.
php --ini
php -i | grep memory_limit
Якщо на сервері встановлено кілька версій PHP, як описано у статті про кілька версій PHP на одному сервері, у crontab обов'язково вказуйте версію явно: /usr/bin/php8.3, а не просто php — інакше завдання може несподівано виконатися під іншою версією після оновлення alternatives.
Пам'ять, таймаути і робочий каталог: часті пастки
| Параметр | Веб (FPM) | Cron (CLI) |
|---|---|---|
| memory_limit | 128M | часто без обмеження (-1) |
| max_execution_time | 30 секунд | 0, обмеження немає |
| робочий каталог | корінь сайту | домашній каталог користувача |
| error_log | лог PHP-FPM | stderr, якщо не задано явно |
Значення з таблиці задаються в різних секціях php.ini: [PHP] впливає на обидва режими, а блок cli/conf.d — лише на CLI. Керування пам'яттю і завантаженням докладно розібрано у статті про memory_limit і upload_max_filesize.
Журналювання виводу завдань cron
- Завжди перенаправляйте stdout і stderr в окремий файл журналу, а не покладайтеся на пошту cron.
- Вказуйте display_errors = Off і log_errors = On у cli/php.ini — вивід помилок у консоль ламає формат, якщо скрипт друкує JSON чи CSV.
- Ротацію лог-файлу cron налаштовуйте через logrotate, інакше файл виросте до гігабайтів за місяць.
- Розбір завдань, що впали, починайте з error_log, а не з припущень — принципи розібрані у статті про логи PHP і розбір фаталів.
- Додавайте на початок кожного завдання date і hostname в журнал — на кластері з кількох серверів інакше не зрозуміти, хто саме писав у журнал.
Підсумок: чек-лист для PHP-завдань у cron
- У crontab вказано повний шлях до бінарника потрібної версії PHP і робочий каталог через cd.
- Вивід команди перенаправлено у файл журналу з ротацією через logrotate.
- Перевірені memory_limit і max_execution_time саме у cli/php.ini, а не в конфігу FPM.
- display_errors вимкнено, log_errors увімкнено, помилки йдуть у файл, а не в пошту root.
- Завдання перевірене вручну від імені того самого користувача, від якого його запускає cron.
Часті питання
Що станеться, якщо у crontab не вказати повний шлях до бінарника PHP?
Якщо вказати просто php без повного шляху, завдання може виконатися під іншою версією або взагалі не запуститися: у процесу cron немає змінної PATH інтерактивної оболонки, і бінарник шукається зовсім не там, де його знаходить звичайний термінал користувача. Тому у crontab завжди прописують повний шлях до бінарника потрібної версії, а не коротке ім'я команди.
Чи можна збільшити memory_limit лише для однієї cron-задачі, не чіпаючи решту?
Так, ліміти пам'яті і часу виконання можна задати прямо у виклику, не торкаючись спільного php.ini: ключі -d memory_limit=... і -d max_execution_time=... передаються php як аргументи командного рядка і діють лише для цього конкретного запуску. Такий підхід зручніший за правку спільного конфігу, коли важка задача всього одна, а решта скриптів мають працювати з лімітами за замовчуванням.
Чому cron-завдання, яке чудово працює вручну з терміналу, падає за розкладом?
Зазвичай річ у відмінному оточенні, а не в самому коді скрипту: в інтерактивній оболонці задані PATH, HOME і локаль, підключений звичний php.ini CLI із потрібними налаштуваннями, а робочий каталог — той, з якого скрипт запустили руками. Процес cron стартує без цього оточення і з домашнього каталогу користувача, тому відносні шляхи до файлів і змінні оточення треба прописувати явно.
Чи треба перенаправляти вивід cron-завдання у файл, якщо помилок зазвичай не буває?
Так, перенаправляти вивід варто завжди, навіть коли завдання зазвичай відпрацьовує без помилок. Без явного редиректу у файл фатальні помилки PHP йдуть у локальну пошту cron, яку на сервері майже ніхто не читає, і перший же збій лишиться непоміченим до скарги користувачів. Журнал із ротацією через logrotate — єдиний надійний спосіб побачити, що саме пішло не так.