Monitoring WordPress Cron via heartbeat checks

Part of my onboarding process and performance optimization for new sites is setting up proper server-level scheduled cron jobs using wp-cli. I use Kinsta hosting for clients and internal sites here at Sprucely Designed and, while they have an excellent hosting stack preconfigured out of the box, they don’t disable the stock WP-Cron or configure a system cron to my liking. So, based on Kinsta’s own knowledgebase recommendations, this is one of the first optimizations I make for new sites.

First, I disable the default WP Cron, by adding this line to the site’s wp-config.php.

define( 'DISABLE_WP_CRON', true );

On Kinsta hosting, this won’t completely disable the cron because Kinsta has a default server cron setup within crontab that performs a curl request to wp-cron.php?server_triggered_cronjob every 15 minutes. They do this as a backup to the default WP cron so that if a site doesn’t receive consistent visitors, the scheduled tasks will still be completed at least every 15 minutes.

3,18,33,48 * * * * curl -kILs -H 'Host: example.com' https://localhost/wp-cron.php?server_triggered_cronjob >/dev/null 2>&1 # Kinsta Primary domain cron

However because Kinsta uses these random times (:03, :18, :33, & :48 in this example), I’ve found that some scheduled tasks don’t run as expected. For example, if a site owner were to schedule a product to be published at 12:00 pm, in this case it wouldn’t actually publish until 12:03 pm. Even worse, if the product were scheduled to publish at 12:05 pm, it wouldn’t actually be published until 12:18 pm (the next time the default scheduled cron runs).

For this reason, I prefer cron jobs to be processed every 5 minutes. To accomplish this change, I SSH into the server and edit the crontab by entering the command crontab -e. Now I can enter a modified command that uses wp-cli to execute the cron events every 5 minutes:

*/5 * * * * /usr/local/bin/wp cron event run --due-now --path=/www/examplesite_123/public --url=https://example.com

Kinsta uses Ansible automations to monitor and update their default cron trigger, so rather than modify their command, I add this as new line at the bottom of the file and comment out their original.

So we end up with a crontab file that looks something like:

MAILTO=""
#Ansible: example-site WordPress cron
#3,18,33,48 * * * * curl -kILs -H 'Host: example.com' https://localhost/wp-cron.php?server_triggered_cronjob >/dev/null 2>&1 # Kinsta Primary domain cron

#Sprucely Preferred Cron
*/5 * * * * /usr/local/bin/wp cron event run --due-now --path=/www/examplesite_123/public --url=https://example.com

There we go. Now we have a server-level cron using wp-cli on Kinsta WordPress hosting.

Monitoring the cron job

Monitoring the cron jobs via Better Uptime helps me track a site’s overall health and ensure scheduled tasks, such as scheduled post publishing, product sale changes, or security scans, are running as expected. I also use Better Uptime for monitoring individual client site uptime and the 3rd party services our clients rely upon over at status.sprucely.net.

Create a heartbeat monitor and set the expected interval equal to the interval set for the wp-cli cron schedule.

To ping this custom URL provided by Better Uptime, we need to add our own scheduled task within WordPress that will be triggered by our wp-cli cron.

To schedule this GET request, I will add the following code to a custom mu-plugin that will schedule a wp_safe_remote_get() request to the URL provided above:

// Cron Heartbeat Monitoring.
add_action( 'sprucely_heartbeat_monitor', 'sprucely_remote_heartbeat_ping' );
/**
 * Ping remote Better Uptime heartbeat URL using key stored in wp-config.php constant.
 *
 * @return void
 */
function sprucely_remote_heartbeat_ping() {
	if ( defined( 'SPRUCELY_CRON_MONITOR_KEY' ) ) {
		wp_safe_remote_get( 'https://betteruptime.com/api/v1/heartbeat/' . SPRUCELY_CRON_MONITOR_KEY );
	}
}
// Add cron interval.
add_filter( 'cron_schedules', 'sprucely_cron_interval' );
/**
 * Create cron interval for every 5 minutes.
 *
 * @param array $schedules Array of defined cron intervals.
 * @return $schedules
 */
function sprucely_cron_interval( $schedules ) {
	$schedules['five_minutes'] = array(
		'interval' => 300, // Time interval in seconds.
		'display'  => esc_html__( 'Every 5 Minutes' ),
	);
	return $schedules;
}
// Schedule event if not already scheduled.
if ( ! wp_next_scheduled( 'sprucely_heartbeat_monitor' ) ) {
	wp_schedule_event( time(), 'five_minutes', 'sprucely_heartbeat_monitor' );
}

Since I manage this custom mu-plugin in GitHub and deploy it to all my managed sites, I use a constant SPRUCELY_CRON_MONITOR_KEY defined in each site’s wp-config.php file that stores the last part of the Better Uptime URL unique to each monitor:

define( 'SPRUCELY_CRON_MONITOR_KEY', 'SXuNXxHWREsstjnXrBMhNx3B' );

Check back in to the heartbeat monitor in Better Uptime and you should now see consistent pings in the monitor:

If you have consistent gaps of “down time” in the monitor. There could be a long-running cron task that doesn’t complete within 5 minutes. Check the list of scheduled events using wp-cli:

wp cron schedule list

Or you can install the WP Crontrol plugin to debug cron schedules from the wp-admin.

Leave a Comment

Your email address will not be published. Required fields are marked *


This site uses Akismet to reduce spam. Learn how your comment data is processed.

Scroll to Top