Scheduling recurring background tasks with WP-Cron
WP-Cron is WordPress’s pseudo-cron system. Unlike a real system cron, it fires when a page is loaded — meaning tasks run approximately on schedule, not exactly. For most WordPress tasks (cleanup, emails, cache rebuilds) this is sufficient. For precise timing, combine WP-Cron with a real system cron.
Problem: How do you schedule a recurring background task in WordPress that runs reliably without a system cron job?
Solution: Register a custom interval, schedule the event on activation, hook your function to it, and clean up on deactivation:
// 1. Add a custom interval (every 15 minutes)
add_filter( 'cron_schedules', 'add_fifteen_min_interval' );
function add_fifteen_min_interval( $schedules ) {
$schedules['fifteen_minutes'] = [
'interval' => 15 * MINUTE_IN_SECONDS,
'display' => __( 'Every 15 Minutes', 'textdomain' ),
];
return $schedules;
}
// 2. Schedule the event on plugin activation
register_activation_hook( __FILE__, 'schedule_my_task' );
function schedule_my_task() {
if ( ! wp_next_scheduled( 'my_hourly_task' ) ) {
wp_schedule_event( time(), 'fifteen_minutes', 'my_hourly_task' );
}
}
// 3. Hook your callback to the event
add_action( 'my_hourly_task', 'run_my_task' );
function run_my_task() {
// your logic here, e.g. delete old transients
global $wpdb;
$wpdb->query( "DELETE FROM {$wpdb->options} WHERE option_name LIKE '%_transient_timeout_%' AND option_value < UNIX_TIMESTAMP()" );
}
// 4. Remove the scheduled event on plugin deactivation
register_deactivation_hook( __FILE__, function() {
wp_clear_scheduled_hook( 'my_hourly_task' );
} );
To trigger the event immediately from the command line (useful for testing):
wp cron event run my_hourly_task
NOTE: Always check wp_next_scheduled() before calling wp_schedule_event() — without this guard, each activation adds a duplicate event. Always call wp_clear_scheduled_hook() on deactivation to prevent orphaned cron entries.