PHP

How to Monitor Cron Jobs in PHP with Cronaman

March 12, 2026·6 min read

The silent failure problem in PHP

PHP cron jobs are everywhere — running on cPanel shared hosting, Plesk VPS instances, and inside Laravel and WordPress installations. They send invoices, sync inventory, generate reports, and purge expired sessions. And they are, quietly, some of the hardest jobs to monitor.

Unlike a web request that returns a 500 status code, a failing PHP cron job produces nothing. The script exits with a non-zero code that nobody reads. Output redirected to /dev/null. No alert. No ticket. You find out when a client asks why their invoice didn't arrive.

What is heartbeat monitoring?

Heartbeat monitoring turns the problem around. Instead of reacting to failures, you configure Cronaman with the expected interval — say, every hour — and your PHP script sends an HTTP ping at the end of every successful run. If the ping doesn't arrive, Cronaman sends you an email. Job crashed, server rebooted, cron entry deleted: all caught.

No extension to install. No library to require. One HTTP call — that's it.

Create a Cronaman monitor

Start by creating the monitor in Cronaman:

  • Sign up at cronaman.dev — free plan, no credit card required
  • Click New Monitor
  • Name it (e.g., "Send invoices") and set the interval to match your cron schedule
  • Copy your ping URL: https://cronaman.dev/ping/php-invoice

Pinging with file_get_contents

file_get_contents is available on virtually every PHP installation — including shared hosting that blocks cURL. It's the simplest way to send a ping:

send_invoices.php
<?php

define('PING_URL', 'https://cronaman.dev/ping/php-invoice');

function sendInvoices(): void {
    // Your job logic here
    echo "Sending invoices...
";
    // ... query DB, generate PDFs, call Resend/SendGrid ...
}

try {
    sendInvoices();
    @file_get_contents(PING_URL);
} catch (Exception $e) {
    echo "Job failed: " . $e->getMessage() . "
";
    exit(1);
}

The @ suppresses PHP warnings if the ping fails (e.g., DNS hiccup) — you don't want a transient network error to mask a successful job run. Note that allow_url_fopen must be enabled in your PHP config, which is the default on most hosts.

Using cURL for more control

If you need a timeout or more control over the HTTP request, cURL gives you both:

send_invoices_curl.php
<?php

define('PING_URL', 'https://cronaman.dev/ping/php-invoice');

function ping(string $url): void {
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_TIMEOUT, 10);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
    curl_exec($ch);
    curl_close($ch);
}

function sendInvoices(): void {
    echo "Sending invoices...
";
    // ... your job logic here ...
}

try {
    sendInvoices();
    ping(PING_URL);
} catch (Exception $e) {
    echo "Job failed: " . $e->getMessage() . "
";
    exit(1);
}

Always set CURLOPT_TIMEOUT. A cron job that hangs on an HTTP call can block the next run if the scheduler doesn't enforce a time limit. 10 seconds is plenty for a simple ping.

Signaling failure explicitly

Append /fail to your ping URL to mark the run as failed immediately, without waiting for the grace period. This is the pattern to use when you handle exceptions explicitly:

send_invoices_robust.php
<?php

define('PING_URL', 'https://cronaman.dev/ping/php-invoice');
define('FAIL_URL', 'https://cronaman.dev/ping/php-invoice/fail');

function ping(string $url): void {
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_TIMEOUT, 10);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_exec($ch);
    curl_close($ch);
}

try {
    // Your job logic here
    sendInvoices();
    ping(PING_URL);    // success
} catch (Exception $e) {
    echo "Job failed: " . $e->getMessage() . "
";
    ping(FAIL_URL);   // signal failure immediately
    exit(1);
}

Scheduling on cPanel / shared hosting

In cPanel, go to Cron Jobs and add your script. A common pattern:

cpanel-cron
/usr/bin/php /home/user/public_html/cron/send_invoices.php >> /dev/null 2>&1

Set the frequency in cPanel to match the interval you configured in Cronaman. Add a grace period of 5–10 minutes to handle load variation on shared servers — they're sometimes slow to spin up PHP.

Laravel scheduled commands

Laravel's scheduler is the cleanest way to manage PHP cron jobs. Add a Cronaman ping using the after callback, which fires after the command completes successfully:

routes/console.php
<?php

use IlluminateSupportFacadesHttp;
use IlluminateSupportFacadesSchedule;

Schedule::command('invoices:send')
    ->hourly()
    ->after(function () {
        Http::get('https://cronaman.dev/ping/php-invoice');
    });

Laravel's Http facade is always available — no extra imports needed. The after hook only runs when the command exits successfully, so the ping is a genuine success signal. For Laravel 11+, this goes in routes/console.php; for Laravel 10 and below, use the App\Console\Kernel class.

Verify your setup

Run the script once from the command line to confirm the ping fires:

terminal
php send_invoices.php

Open the Cronaman dashboard. The monitor should show "healthy" within a few seconds. If it stays grey, verify that allow_url_fopen is enabled (for file_get_contents) or that the cURL extension is loaded (for curl). On cPanel, you can check both in the PHP configuration section.

Once confirmed, your PHP cron jobs are monitored. Missed run, exception, server issue — you'll get an email immediately, before anyone else notices.

More cron monitoring guides

Using a different language? The pattern is the same everywhere:

Start monitoring your PHP cron jobs

Free forever for up to 3 monitors. No credit card required. Set up in under 2 minutes.

Start monitoring free