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

> PHP у cron: різниця між CLI і FPM, власний php.ini, пам'ять і таймаути, правильний crontab і журналювання виводу завдань.

## Чим 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 на одному сервері](https://www.zevshost.net/uk/kb/php-multi-version-linux/), у 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](https://www.zevshost.net/uk/kb/php-ini-memory-upload-limits/).

## Журналювання виводу завдань cron

- Завжди перенаправляйте stdout і stderr в окремий файл журналу, а не покладайтеся на пошту cron.
- Вказуйте display\_errors = Off і log\_errors = On у cli/php.ini — вивід помилок у консоль ламає формат, якщо скрипт друкує JSON чи CSV.
- Ротацію лог-файлу cron налаштовуйте через logrotate, інакше файл виросте до гігабайтів за місяць.
- Розбір завдань, що впали, починайте з error\_log, а не з припущень — принципи розібрані у статті [про логи PHP і розбір фаталів](https://www.zevshost.net/uk/kb/php-logi-oshibok-razbor/).
- Додавайте на початок кожного завдання 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 — єдиний надійний спосіб побачити, що саме пішло не так.

---

Джерело: https://www.zevshost.net/uk/kb/php-cli-vs-fpm-cron/
