Skip to main content

PHP in Cron: How CLI Differs from FPM in Practice

PHP · · 5 min read
Illustration for “PHP in Cron: How CLI Differs from FPM in Practice”

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, 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

ParameterWeb (FPM)Cron (CLI)
memory_limit128Moften unlimited (-1)
max_execution_time30 seconds0, no limit
working directorysite rootuser home directory
error_logPHP-FPM logstderr, 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.

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.
  • 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.

Was this article helpful?
← Back to Knowledge Base Ask Support