2026年11月12日

2026年11月12日

WP-Cron in a Plugin: Schedule, Clear, Check with WP-CLI, Server Cron

This article is a localized edition of the Japanese original. Read the Japanese original

WP-Cron is the task scheduler built into WordPress. Unlike the server's cron, it checks for events whose scheduled time has passed whenever someone visits the site, and if it finds any, it calls wp-cron.php in the background to run them. Plugins use it for recurring jobs such as sending emails in bulk, deleting old data, or importing from an external API. The functions and commands in this article were checked against the function reference on developer.wordpress.org and the WP-CLI command reference as of November 2026.

How WP-Cron works, and what to watch for

  • Execution is triggered by page visits. If nobody visits the site, events do not run even after their scheduled time has passed.
  • Events are managed by hook name. At the scheduled time, do_action() is called with that hook name, and the function you registered with add_action() runs.
  • Scheduled events are stored in the wp_options table (in the option named cron). They stay there even after a plugin is deactivated, so you have to remove them yourself.

Register a recurring task

This example goes in the plugin's main file. It schedules a daily event on activation and removes it on deactivation.

<?php
/**
 * Plugin Name: My Cron Example
 */

// 1. Attach the task to the hook (place this where it runs on every load)
add_action( 'myplugin_daily_task', 'myplugin_run_daily_task' );

function myplugin_run_daily_task() {
	global $wpdb;

	// Example: delete logs older than 30 days from a custom table (assumes created_at is stored in UTC)
	$table = $wpdb->prefix . 'myplugin_logs';
	$wpdb->query(
		$wpdb->prepare(
			"DELETE FROM {$table} WHERE created_at < %s",
			gmdate( 'Y-m-d H:i:s', time() - 30 * DAY_IN_SECONDS )
		)
	);
}

// 2. Schedule the event on activation
register_activation_hook( __FILE__, 'myplugin_activate' );

function myplugin_activate() {
	if ( ! wp_next_scheduled( 'myplugin_daily_task' ) ) {
		wp_schedule_event( time() + HOUR_IN_SECONDS, 'daily', 'myplugin_daily_task' );
	}
}

// 3. Clear the event on deactivation
register_deactivation_hook( __FILE__, 'myplugin_deactivate' );

function myplugin_deactivate() {
	wp_clear_scheduled_hook( 'myplugin_daily_task' );
}

Key points:

  • Put add_action() somewhere in the file that runs on every load, not inside the activation callback. If an event is scheduled but no function is attached to the hook, nothing happens when the time comes.
  • The first argument of wp_schedule_event( first run time, interval, hook name ) is a Unix timestamp (UTC). WordPress ships with four intervals: hourly (1 hour), twicedaily (12 hours), daily (1 day), and weekly (1 week, since WordPress 5.4).
  • To avoid scheduling the same event over and over, check with wp_next_scheduled() before scheduling.
  • On deactivation, wp_clear_scheduled_hook() removes all events for that hook name (and arguments) at once. If you use wp_unschedule_event() instead, get the timestamp with wp_next_scheduled() first and pass it in.

Add a custom interval

Intervals that WordPress does not provide, such as every 5 or 15 minutes, are added with the cron_schedules filter. weekly already exists in WordPress, so you do not need to add it.

<?php
add_filter( 'cron_schedules', function ( $schedules ) {
	$schedules['myplugin_every_15_minutes'] = array(
		'interval' => 15 * MINUTE_IN_SECONDS,
		'display'  => 'Every 15 minutes',
	);
	return $schedules;
} );

register_activation_hook( __FILE__, function () {
	if ( ! wp_next_scheduled( 'myplugin_sync_task' ) ) {
		wp_schedule_event( time(), 'myplugin_every_15_minutes', 'myplugin_sync_task' );
	}
} );

The cron_schedules filter has to be loaded both when the event is scheduled and when it runs. Put it at the top level of the plugin file (outside any function). Prefix the interval name so it does not collide with other plugins.

Because WP-Cron is triggered by visits, scheduling an event every 5 minutes does not guarantee that it runs every 5 minutes. If you need it to run on schedule, combine it with the server cron described below.

Schedule a one-off event

When you want to defer heavy work, for example when a post is published, use wp_schedule_single_event().

<?php
// When a post is published, schedule follow-up work one minute later
add_action( 'publish_post', function ( $post_id ) {
	wp_schedule_single_event( time() + MINUTE_IN_SECONDS, 'myplugin_process_post', array( $post_id ) );
} );

// Runs at the scheduled time. Arguments arrive in the order of the array passed when scheduling
add_action( 'myplugin_process_post', function ( $post_id ) {
	$post = get_post( $post_id );
	if ( ! $post ) {
		return;
	}
	// Put the heavy work here (image processing, sending to an external API, etc.)
	update_post_meta( $post_id, '_myplugin_processed', gmdate( 'c' ) );
} );

wp_schedule_single_event() will not create a new event if an event with the same hook name and the same arguments already exists within 10 minutes. If you include the post ID in the arguments, each post is treated as a separate event.

Check and run events with WP-CLI

On servers where WP-CLI is available, you can check scheduled events with these commands.

# List scheduled events (hook, next run, recurrence)
wp cron event list --fields=hook,next_run_relative,recurrence

# List registered intervals (including those added with cron_schedules)
wp cron schedule list

# Run the events for a hook right now
wp cron event run myplugin_daily_task

# Run all events that are due
wp cron event run --due-now

# Delete all events for a hook
wp cron event delete myplugin_daily_task

# Schedule a test event (starting now, daily)
wp cron event schedule myplugin_daily_task now daily

# Check that WP-Cron spawning (loopback) works
wp cron test

If wp cron test fails, the server may not be able to connect to its own URL (because of a firewall, Basic authentication, DNS settings, and so on). Tools → Site Health in the admin also reports late scheduled events and loopback connection problems.

On low-traffic sites, run it from the server cron

On low-traffic sites, or when a task must run at a fixed time, stop the visit-triggered execution and call WP-Cron from the server's cron at regular intervals instead.

First, add this line to wp-config.php, above the line that says "That's all, stop editing!"

define( 'DISABLE_WP_CRON', true );

Then register one of the following in the server's cron (crontab -e, or the cron settings in your hosting control panel). The examples run every 5 minutes.

# Using WP-CLI (adjust the paths to your server)
*/5 * * * * cd /var/www/html && /usr/local/bin/wp cron event run --due-now --quiet

# Calling wp-cron.php over HTTP
*/5 * * * * wget -q --delete-after "https://example.com/wp-cron.php?doing_wp_cron"

The WP-CLI method is not affected by web server timeouts. Running the cron job as the same user that owns the WordPress files makes permission failures less likely. If you set DISABLE_WP_CRON but forget to register the cron job, no events will run at all, so check with wp cron event list that the next run times keep moving forward. Server-side setup is also covered in "Running WordPress WP-Cron from the server cron."

Notes

  • Write recurring tasks so they can pick up again on the next run if they stop partway. WP-Cron does not report failures, so log inside the task if you need a record.
  • If one run takes a long time, process items in smaller batches. If the task hits PHP's execution time limit, it stops partway through.
  • In the admin, a plugin can only be deleted after it has been deactivated, so if your deactivation hook clears the events, nothing is left in the database after deletion.

Questions about WordPress? The publisher of this site is based in Japan and replies in Japanese. Contact (Japanese)

お気軽にご相談ください

お見積りへ お問い合わせへ
▲