PHP cookbook · Beginner
Run scheduled jobs (cron) reliably in PHP
Run nightly reports and cleanups without overlapping runs or silent failures.
Approach
Use cron (or a systemd timer) to invoke a PHP CLI script, guard it with a lock so runs never overlap, log start/finish/duration, exit non-zero on failure, and send a heartbeat to a monitoring service (dead man's switch) so missed runs alert you.
Solution
<?php
// bin/nightly-report.php
declare(strict_types=1);
require __DIR__ . '/../vendor/autoload.php';
$lock = fopen('/tmp/nightly-report.lock', 'c');
if (!flock($lock, LOCK_EX | LOCK_NB)) {
fwrite(STDERR, "Previous run still in progress\n");
exit(0);
}
$start = microtime(true);
try {
$count = generateNightlyReport(createPdo());
$logger->info('nightly report done', ['rows' => $count, 'seconds' => round(microtime(true) - $start, 1)]);
file_get_contents(getenv('HEARTBEAT_URL')); // e.g. healthchecks.io ping
exit(0);
} catch (Throwable $e) {
$logger->error('nightly report failed', ['exception' => $e]);
exit(1);
} finally {
flock($lock, LOCK_UN);
}
# crontab -e (UTC)
15 2 * * * /usr/bin/php /var/www/app/bin/nightly-report.php >> /var/log/app/cron.log 2>&1
Pitfalls
- Overlapping runs when a job takes longer than its interval.
- Cron running a different PHP version/ini than the web server.
- On multiple servers, every server runs the job (use a DB/Redis lock or run on one host).
Variations
- Laravel scheduler:
$schedule->command('report:nightly')->dailyAt('02:15')->withoutOverlapping()->onOneServer(). - Symfony Scheduler component with Messenger.
Further reading: PHP manual: flock
Reference: https://www.php.net/manual/en/function.flock.php
More PHP recipes
- Add CSRF protection to PHP forms
- Add health check endpoints to a PHP app
- Add structured logging with request IDs in PHP
- Build a secure password reset flow in PHP
- Cache expensive queries with Redis in PHP
- Call external APIs with timeouts and retries in PHP
- Handle file uploads securely in PHP
- Implement rate limiting in a PHP API