# PHP in Cron: How CLI Differs from FPM in Practice

> PHP in cron: the difference between CLI and FPM, a separate php.ini, memory and timeouts, correct crontab syntax, and job logging.

## How PHP CLI Differs from PHP-FPM for Cron Jobs

PHP-FPM handles HTTP requests through Nginx and runs as a pool of workers sharing one php.ini with limits tuned for web traffic. PHP CLI is a separate binary, /usr/bin/php8.3, launched from the command line or from crontab, and it reads its own config, usually from /etc/php/8.3/cli/php.ini. The values of memory\_limit, max\_execution\_time, and error\_reporting can differ between CLI and FPM, which is a common reason a script works fine in the browser but the same script fails on schedule, or the other way around.

The second difference is the environment. A cron process has none of the PATH, HOME, and locale variables an interactive shell has, and no default working directory: it always starts from the home directory of the user that owns the cron job.

## How to Write a Cron Job Correctly

A crontab rule should specify the full path to the PHP binary, the full path to the script, and the working directory if the script uses relative file paths.

```
*/5 * * * * cd /var/www/app && /usr/bin/php8.3 artisan schedule:run >> /var/log/app/cron.log 2>&1
```

The && operator guarantees that php only runs after successfully changing into the directory, and 2>&1 redirects errors into the same log file as regular output. Without this, fatal PHP errors disappear silently: cron mails them to a local mailbox nobody reads.

## A Separate php.ini for CLI

You can check which config CLI actually uses with a single command — it shows the real file path instead of a guess.

```
php --ini
php -i | grep memory_limit
```

If the server has several PHP versions installed, as described in the article [on running multiple PHP versions on one server](https://www.zevshost.net/en/kb/php-multi-version-linux/), always specify the version explicitly in crontab: /usr/bin/php8.3, not just php — otherwise the job can suddenly run under a different version after an alternatives update.

## Memory, Timeouts, and Working Directory: Common Pitfalls

| Parameter | Web (FPM) | Cron (CLI) |
| --- | --- | --- |
| memory\_limit | 128M | often unlimited (-1) |
| max\_execution\_time | 30 seconds | 0, no limit |
| working directory | site root | user home directory |
| error\_log | PHP-FPM log | stderr, unless set explicitly |

The values in this table are set in different sections of php.ini: \[PHP\] affects both modes, while the cli/conf.d block affects only CLI. Memory and upload management are covered in detail in the article [on memory\_limit and upload\_max\_filesize](https://www.zevshost.net/en/kb/php-ini-memory-upload-limits/).

## Logging Cron Job Output

- Always redirect stdout and stderr to a dedicated log file instead of relying on cron's mail.
- Set display\_errors = Off and log\_errors = On in cli/php.ini — printing errors to the console breaks the format if the script outputs JSON or CSV.
- Configure log rotation for the cron log through logrotate, otherwise the file grows to gigabytes within a month.
- Start investigating a failed job from error\_log, not from guesses — the approach is covered in the article [on PHP logs and diagnosing fatal errors](https://www.zevshost.net/en/kb/php-logi-oshibok-razbor/).
- Prepend date and hostname to every log line for each job — on a cluster of several servers, otherwise there is no way to tell which one wrote it.

## Summary: Checklist for PHP Jobs in Cron

- Crontab specifies the full path to the correct PHP version binary and the working directory via cd.
- Command output is redirected to a log file with rotation configured through logrotate.
- memory\_limit and max\_execution\_time were checked in cli/php.ini, not in the FPM config.
- display\_errors is off, log\_errors is on, and errors go to a file instead of root's mailbox.
- The job was tested manually as the same user account that cron uses to run it.

## Frequently asked questions

### What happens if crontab doesn't specify the full path to the PHP binary?

If you write just php without a full path, the job may run under a different version or fail to start, since cron has no interactive shell PATH and looks for the binary somewhere else than a user terminal would. That is why crontab entries always spell out the full binary path rather than the short command name.

### Can memory\_limit be raised for just one cron job without touching the rest?

Yes, memory and execution-time limits can be set in the command without editing the shared php.ini: the -d memory\_limit and -d max\_execution\_time flags are passed to php as command-line arguments and apply only to that one invocation. This beats editing the shared config when only a single heavy job needs it while the rest keep running with the default limits.

### Why does a cron job that runs fine manually from the terminal fail on schedule?

Usually it comes to the environment, not the script's code: an interactive shell has PATH, HOME, and a locale set, loads the familiar CLI php.ini, and runs from wherever the script was launched by hand. A cron process starts with none of that, from the user's home directory, so relative file paths and environment variables need to be spelled out.

### Is it necessary to redirect a cron job's output to a file if errors are rare?

Yes, redirecting output is worth doing always, even for a job that normally runs clean. Without an explicit redirect to a file, fatal PHP errors go into cron's local mailbox, which almost nobody reads, so the first failure goes unnoticed until someone complains. A log file with rotation through logrotate is the only reliable way to see what went wrong.

---

Source: https://www.zevshost.net/en/kb/php-cli-vs-fpm-cron/
