Cron Jobs

Creating Cron Jobs in PHP

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

PHP is one of the most popular programming languages in the world, and helped shape the evolution of the web. Combine its popularity with its flexibility as a scripting language, and it's not surprising that PHP is one of the most common languages for cron jobs.

Traditionally, a cron job is a command on a Linux/Unix server being executed on a user-defined schedule using cron scheduler. While still true today, the definition has expanded to generally means a piece of code running in the background at a scheduled time.

An often defining characteristic of a cron job is the cron schedule syntax_. These schedules (like 5 4 * * *) are so hard to understand that over 700,000 visitors per month use Crontab Guru to translate them into english.

In this article, you’ll learn how to run PHP scripts as cron jobs, and explore some of the ways that popular PHP frameworks and tools enable you to add cron functionality while avoiding some of the pitfalls of traditional cron..

Run a PHP script as a cron job

One of the great things about PHP is the ability to invoke any file as a script. That means any file can 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();
}
?>

This script can be executed from the command prompt as:

php /path/to/daily_email.php

It's useful to manually test any new script before running it as a cronjob. 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

If you're already using a PHP framework instead of a standalone script, you may have even more convenient tools for scheduling cron jobs directly from PHP instead of dealing with the crontab. Laravel has become the most popular PHP web framwork, and it comes with a queue-based scheduler that can replace your need for cron.

It also offers developers a much simpler syntax (though it still support cron syntax as well) for expressing schedules. For example, if you have an artisan command to send email notifications that needs to be triggered daily, you would write this:

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

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 PHP application:

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

Another benefit of the Laravel Scheduler is that it uses a Queue to dispatch jobs instead of running them synchronously (as traditional cron does).

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']); });`}