Back to sensacat

Home  /  Integration guides

· SensaCat Team

How to Monitor Cron Jobs in PHP

PHP CLI uses a different php.ini from your web server. That single fact explains most scripts that work in the browser and fail in cron.

Monitoring a plain PHP cron script means handling three things PHP makes awkward: fatal errors that skip your catch blocks, a CLI configuration that differs from your web server's, and output that goes nowhere by default.

Use register_shutdown_function, not just try/catch

A try/catch will not fire on a fatal error. Running out of memory, exceeding max_execution_time, or calling a method on null in older PHP versions all bypass exception handling entirely and terminate the script.

register_shutdown_function runs on every exit path including fatal errors, which makes it the only reliable place to report on what happened.

Setting $completed as the final statement is the whole design. Any exit before that line, for any reason including a fatal error or an exit() buried in a helper, produces a failure ping with the error attached.

CLI has its own php.ini

This catches people repeatedly. The PHP CLI binary reads a different configuration file from PHP-FPM, and the values that matter differ.

Setting Typical PHP-FPM value Typical CLI value
max_execution_time 30 seconds 0 (unlimited)
memory_limit 128M Often -1 or much higher
display_errors Off Often On
error_log Set to a file Frequently unset
Loaded extensions Per the FPM pool Can differ, especially opcache

The unset error_log is the one that hurts. A fatal error in a cron script writes to stderr, cron tries to mail it, no mail transfer agent exists, and the message is gone. The script failed and there is no record anywhere.

Write the crontab entry properly

Use the versioned binary path rather than php. Servers running more than one PHP version will resolve php to whichever is linked, and a routine upgrade can change that without anyone deciding to.

The 2>&1 is not optional. Without it you capture stdout and lose exactly the stderr output that describes the failure.

Preventing overlapping runs

A script that occasionally takes longer than its interval will eventually have two copies running at once, which for anything touching a database is a genuine problem.

flock -n exits immediately if the lock is held. That is usually what you want, and it means a skipped run produces no ping, so your monitor will eventually tell you the job has been blocked. A job that is permanently blocked by a stuck lock looks exactly like a job that stopped running, which is correct: it did.

Laravel applications should use the scheduler instead, covered in monitoring cron jobs in Laravel.