До основного вмісту

PHP у cron: чим CLI відрізняється від FPM на практиці

PHP · · 5 хв читання
Ілюстрація до статті «PHP у cron: чим CLI відрізняється від FPM на практиці»

Чим 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_limit128Mчасто без обмеження (-1)
max_execution_time30 секунд0, обмеження немає
робочий каталогкорінь сайтудомашній каталог користувача
error_logлог PHP-FPMstderr, якщо не задано явно

Значення з таблиці задаються в різних секціях 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 — єдиний надійний спосіб побачити, що саме пішло не так.

Чи була стаття корисною?
← Назад до бази знань Поставити питання підтримці