Cron Jobs

Scheduling Cron Jobs in PHP

By: Keanan Koppenhaver|Last Updated: Feb 17, 2023

PHP helped shape the evolution of the web, and its incredible popularity and flexibility as a scripting language, it's not surprising that PHP is also a popular language for writing cron jobs.

In this guide you’ll learn how to run any PHP script as a cron job. We also explore some of the ways that popular PHP frameworks and tools provide cron-like solutioins while avoiding some of the pitfalls of traditional Linux cron jobs.

What is a cron job?

Historically a cron job is a command on a Unix server that is executed on a schedule by the cron daemon. Now broadly used by many background task systems, the term cron job generally means a piece of code running in the background on a repeatable schedule.

What the * * * * * ?!?

One of the strangest parts of a cron job is the scheduling syntax, e.g.  */3 2 * * 0-3.

Reminiscent of regular expressions, it is very hard to remember. See our guide on cron schedule syntax or use crontab.guru to translate a cron expression to english.

Run a PHP script as a cron job

One of the many wonderful things about PHP is the ability to invoke any file as a script. That means any file can quickly become a cron job. As an example, say you have a PHP file called daily_email.php with the following psuedo-code that sends a daily email:

<?php
use Core\Models\Customer;

foreach( Customer::all() as $customer ) {
	$customer::sendDailyReport();
}
?>

The first step to running a PHP script as a cron job is to manually run it from the command prompt.

php /path/to/daily_email.php

Note: Even if you don't see any errors when running it directly, you will want to take a look at the important considerations listed below to understand some of the ways that cron jobs can silently fail._

Editing the Crontab

Once you have a script that you can manually execute, you're ready to configure it for execution by the cron scheduler. This is done by editing a crontab file, which contains the configuration for all of the cron jobs that either a particular user or operating system has scheduled.

From the command line on your server, run crontab -e to access your crontab in a text editor. From there, you can paste or write in your crontab entry to run your PHP script. To run our cron job a midnight every day we will add the following line:

0 0 * * * php /path/to/php/daily_email.php

What the * * * * ?!?

The syntax for cron schedules is terse and tricky to remember. Our guide on Linux cron jobs covers cron syntax in detail.

Important Considerations

While it is straightforward to trigger a script this way, there are a number of ways cron jobs can fail silently:

1. Always use absolute paths

The current working directory used by cron is usually (but not always) the job owner's home directory. Avoid any confusion by exclusively using fully-qualified paths when you add the job to the crontab file. If your script is invoking any other commands, it should use a fully qualified path or set its own working directory before invoking them.

2. Make sure environment variables are accessible

Even though cron runs your script using a real user account, it does not trigger an interactive session, so any environment variables you may be loading in .bash_profile or .bashrc will not be available.

3. Add monitoring when necessary

Because cron jobs run in the background, and can often fail silently, consider using a tool like Cronitor to make sure your cron jobs are running on schedule, and exiting with the output that you'd expect.

Run a cron job with Laravel

Laravel has become the most popular PHP web framwork, and it comes with a queue-based scheduler that can replace your need for running traditional cron jobs. There are several benefits of this approach - it makes it easier to track (version control), deploy and scale your jobs.

It also offers developers a much easier syntax for expressing schedules (and still supports cron syntax). For example, if you have an artisan command to send email notifications that needs to be triggered daily at 1:00 pm, you would write this:

$schedule->command('emails:send')->daily()->at('13:00');

You can also execute shell commands while still using the Laravel scheduler by passing the entirety of the shell command into the scheduler's exec function. Here, we execute a NodeJS script from within our Laravel/PHP application:

$schedule->exec('node /home/forge/script.js')->daily();

Schedule cron jobs with Google Cloud Scheduler

Laravel’s built-in scheduler is great, but if you’re using a different framework, or if you need to integrate other tools into your workflow, a third-party scheduler might fit your needs. For example, Google Cloud Scheduler can be configured to make a request to any URL on a specified scehdule.

In addition to being accessible through the native Google Cloud API endpoints and infrastructure, Google Cloud Scheduler has a well-documented PHP library for easy integration into whatever PHP project you happen to be running. After installing the package with Composer, you could use a block of code like the following to create a new job through Google Cloud Scheduler:

require 'vendor/autoload.php';

use Google\Cloud\Scheduler\V1\HttpTarget;
use Google\Cloud\Scheduler\V1\CloudSchedulerClient;
use Google\Cloud\Scheduler\V1\Job;
use Google\Cloud\Scheduler\V1\Job\State;

$client = new CloudSchedulerClient();
$projectId = '[MY_PROJECT_ID]';
$location = 'us-central1';
$parent = CloudSchedulerClient::locationName($projectId, $location);
$job = new Job([
    'name' => CloudSchedulerClient::jobName(
        $projectId,
        $location,
        uniqid()
    ),
    'http_target' => new HttpTarget([
        ‘uri’ => ‘https://yourcronendpoint.com/cron-endpoint’
    ]),
    'schedule' => '* * * * *'
]);
$client->createJob($parent, $job);

You might notice the syntax for the actual job itself looks very similar to cron syntax, except that your job is running on a remote server as part of Google Cloud instead of locally on your own server. The code above will need to be run to set up the cron job, but after it’s been run once, Google Cloud Scheduler will run your cron job on the schedule you specified.

This is a popular method for getting started with cron jobs, but there are a few concerns with this approach that could result in unreliable cron job execution:

  1. Be careful with timeouts

    Because this job is triggered by a web request there are at least two places it might hit a timeout that kills your job before it has completed all of its work. First, the service you are using likely has implemented a timeout. For example, Google Cloud Scheduler has a 30 minute timeout on all requests. Separately, your webserver or application platform may also enforce a timeout. It is common for a nginx webserver to have timeouts as low as 60 seconds, and if you are running your application in a Serverless environment, AWS Lambda invocations are limited to just 15 minutes. When a timeout is hit your job will be killed immediately, no matter how close or far it is from finishing its job.

  2. Log ingestion limitations

    Writing audit logs from a cron job as it runs is critical to verifying and troubleshooting your jobs, but this can be tricky when you are using a 3rd party service to invoke your jobs. Third party services will limit both the total bytes allowed and their retention period. This truncation may even happen silently so it's important to verify and validate your job logs after scheduling a new job.

Monitor PHP cron jobs

No matter which tool you use, it's important to consider how you will monitor your cron jobs — it's too easy for them to fail silently. That's where a tool like Cronitor comes in. Cronitor makes it easy to monitor your cron jobs, and will alert you immediately when a job fails, or doesn't run on time.

Cronitor's PHP SDK makes it easy to add monitoring with just a couple of lines of code

Monitor any PHP Cron Job

$cronitor = new Cronitor\\Client('<cronitor api key here'>);

# the start and end of the function execution are monitored
# and any errors are reported automatically (and reraised)
$cronitor->job('weekly-report-job', function() {
  WeeklyReportJob::run();
});

Monitor a Laravel Cron Job

// in your .env file
CRONITOR_API_KEY='<cronitor api key here>'

// in your services.php file
'cronitor' => [
    'api_key' => env('CRONITOR_API_KEY'),
],

// in your Kernel.php file
$cronitor = new Cronitor\\Client(config('services.cronitor.api_key'));

$schedule->call(function() {
  $cronitor->job('${displayKey}', new DailyMetricsTask);
})->dailyAt('14:05');

// Or, Use Laravel's built in task hooks to wrap Artisan commands with telemetry events.
$taskName = 'emails:send'
$monitor = $cronitor->monitor($taskName);
$schedule->command($taskName)
  ->daily()
  ->before(function() { $monitor->ping(['state' => 'run']); })
  ->onSuccess(function() { $monitor->ping(['state' => 'complete']); })
  ->onFailure(function() { $monitor->ping(['state' => 'fail']); });`}
Previous
Python Cron Jobs