Skip to content

WPShout Newsletter

Sign up for news, tips, and insights to help you build better websites.

Newsletter - Top banner

How to Use wp_schedule_event() for WordPress Cron Jobs

WP-Cron lets WordPress run scheduled tasks such as publishing posts, checking for updates, processing queues, and running plugin maintenance. It is useful for recurring work, but it does not behave exactly like the system cron service on a server.

This guide explains how to schedule a recurring event with wp_schedule_event, prevent duplicate events, remove a task when a plugin is deactivated, add a custom interval, and troubleshoot jobs that do not run when expected.

The examples use prefixed names, one consistent argument array, and basic error handling so they can be adapted safely inside a plugin.

WPShout featured graphic for scheduling, testing, and cleaning up WordPress cron jobs

OPEN FOR ENROLLMENT

Join Modern WordPress Fast Track today!

02:04 min • Watch the course overview

Quick answer: Register the callback with add_action(), schedule it once with wp_schedule_event(), check wp_next_scheduled() before creating another event, and clear the hook with wp_clear_scheduled_hook() when the plugin is deactivated. WP-Cron runs when WordPress gets an opportunity to process due events, usually during a page load.

What is WP-Cron?

WP-Cron is WordPress’s built-in scheduler for time-based tasks. Its recurring schedules use intervals to simulate a traditional cron system.[1]

If a site receives no visits around the scheduled time, the event may run later when the next suitable request arrives. That makes WP-Cron convenient for many sites, but it is not a guarantee that a callback will start at an exact wall-clock time.

WP-Cron stores recurring events as a hook, a recurrence interval, an initial timestamp, and optional arguments. The hook is the bridge between the scheduled event and the PHP callback that performs the work.

How to schedule a recurring WordPress event

The WordPress scheduling handbook recommends registering the callback first, then scheduling the event. It also warns that calling the scheduling function repeatedly can create duplicate events.[2]

The current function signature is:

wp_schedule_event(
    $timestamp,
    $recurrence,
    $hook,
    $args = array(),
    $wp_error = false
);Code language: PHP (php)
  1. $timestamp is the Unix timestamp for the first run.
  2. $recurrence is a registered schedule such as hourly, twicedaily, daily, or weekly.
  3. $hook is the name passed to add_action().
  4. $args is an optional array of values passed to the callback.
  5. $wp_error is the fifth argument. Set it to true when the code needs a WP_Error result instead of a silent failure.

The current function reference documents these parameters, the four built-in recurrences, and the optional error return. It also notes that a due action runs when someone visits the site after its scheduled time.[3] A recurring event should normally be created during plugin activation or another controlled setup action, rather than on every page load.

Avoid duplicate events with wp_next_scheduled()

Before scheduling an event, check whether the same hook and arguments are already present. wp_next_scheduled() returns the next Unix timestamp for a matching event or false when no event is scheduled.[4]

A complete plugin-safe example

The following example schedules a daily task when a plugin is activated, passes one exact argument array through the lifecycle, runs an idempotent operation, logs failures, and removes the matching event when the plugin is deactivated.

<?php
defined( 'ABSPATH' ) || exit;

const WPSHOUT_CRON_HOOK = 'wpshout_refresh_marker';

register_activation_hook( __FILE__, 'wpshout_refresh_activate' );
add_action( WPSHOUT_CRON_HOOK, 'wpshout_refresh_run', 10, 1 );
register_deactivation_hook( __FILE__, 'wpshout_refresh_deactivate' );

function wpshout_refresh_activate() {
    $args = array( 'wpshout' );

    if ( false !== wp_next_scheduled( WPSHOUT_CRON_HOOK, $args ) ) {
        return;
    }

    $scheduled = wp_schedule_event(
        time() + HOUR_IN_SECONDS,
        'daily',
        WPSHOUT_CRON_HOOK,
        $args,
        true
    );

    if ( is_wp_error( $scheduled ) ) {
        error_log(
            'WPShout cron activation failed: ' . $scheduled->get_error_message()
        );
    }
}

function wpshout_refresh_run( $source ) {
    if ( 'wpshout' !== $source ) {
        return;
    }

    delete_transient( 'wpshout_example_cache' );
}

function wpshout_refresh_deactivate() {
    $args = array( 'wpshout' );

    $cleared = wp_clear_scheduled_hook(
        WPSHOUT_CRON_HOOK,
        $args,
        true
    );

    if ( is_wp_error( $cleared ) ) {
        error_log(
            'WPShout cron cleanup failed: ' . $cleared->get_error_message()
        );
    }
}Code language: HTML, XML (xml)

The marker update is only an example of work that can run on a schedule. A real callback might process a queue, refresh an API response, remove expired records, or generate a report. The operation should be safe to run more than once because a scheduled request can be delayed or retried.

Use a unique prefix for the hook and function names. Generic names such as cleanup or run_task can collide with another plugin or theme. If a callback accepts arguments, use the same values when checking, scheduling, and clearing the event.

wp_clear_scheduled_hook() clears every event with the matching hook and arguments. With its third argument set to true, a failure can be returned as WP_Error and handled explicitly.[5] The error_log() calls above are compact examples; production code should use the site’s normal logging and monitoring path.

Schedule the first run at a site-local time

A Unix timestamp is absolute, while a time such as 3:00 a.m. is local to the site or server. wp_timezone() returns the timezone configured in WordPress.[7]

$site_timezone = wp_timezone();
$first_run = new DateTimeImmutable( 'tomorrow 03:00', $site_timezone );
$first_run_timestamp = $first_run->getTimestamp();Code language: PHP (php)

Use $first_run_timestamp in place of the timestamp in the complete activation example, while keeping its duplicate guard, arguments, and error handling. If the task must remain at exactly 3:00 a.m. through daylight-saving changes, reschedule the next single event after each run or use a server scheduler with a clearly defined timezone.

How to add a custom WP-Cron interval

WordPress provides common schedules, but a plugin can register another interval with the cron_schedules filter. Each non-default schedule needs a unique key, an interval in seconds, and a display name.[11]

add_filter( 'cron_schedules', 'wpshout_add_five_minute_schedule' );

function wpshout_add_five_minute_schedule( $schedules ) {
    if ( ! isset( $schedules['wpshout_five_minutes'] ) ) {
        $schedules['wpshout_five_minutes'] = array(
            'interval' => 5 * MINUTE_IN_SECONDS,
            'display'  => __( 'Every Five Minutes', 'wpshout' ),
        );
    }

    return $schedules;
}Code language: PHP (php)

After the filter is loaded, replace daily in the complete activation example with wpshout_five_minutes. Keep the same guard, arguments, fifth error argument, and error check.

Short intervals can increase server load, especially on sites with frequent traffic. A very short interval also does not solve the underlying timing limitation of WP-Cron. Use the longest interval that meets the task’s actual requirement.

Add error handling to the callback

A cron callback runs separately from the page that originally scheduled it. Errors should therefore be recorded or handled inside the callback rather than displayed to a visitor.

  • Check return values from WordPress functions that can return WP_Error.
  • Use a short timeout for remote requests.
  • Validate response codes and response content before saving data.
  • Make the operation idempotent so a retry does not duplicate records or send duplicate messages.
  • Log enough context to identify the hook and failure, but do not write secrets or personal data to the log.

For larger jobs, split the work into smaller batches and store progress between runs. A callback that exceeds the PHP request limit can time out even when the schedule itself is working correctly.

How to verify a scheduled event

WP-CLI provides a direct way to inspect scheduled events and their next-run fields.[8] It can also run a named hook immediately for testing.[9]

wp cron event list --fields=hook,next_run_gmt,next_run_relative,recurrence

wp cron event run wpshout_refresh_markerCode language: PHP (php)

The first command shows whether the hook exists and when it is expected to run. The second runs the hook immediately for testing.

WP-CLI can confirm that an event is registered, but it cannot prove that the callback completed successfully. Check the callback’s output, saved state, logs, or external side effect as well.

WP-CLI output after running due WordPress cron events
wp cron event run --due-now runs every event that is currently due.

Inspect events from the WordPress admin

WP Crontrol can show scheduled hooks, recurrence names, arguments, callbacks, and next-run times in the WordPress admin area.[10] It is useful when shell access is unavailable. Treat the dashboard as an inspection tool and avoid deleting events that belong to core, another plugin, or the active theme.

When to use the system task scheduler

Sites with low traffic or strict timing requirements may need a real server scheduler to request wp-cron.php at a chosen interval. WordPress documents the paired system-scheduler and configuration path.[6]

*/5 * * * * wget --delete-after https://example.com/wp-cron.phpCode language: JavaScript (javascript)
The five timing fields in a system cron expression
A system cron expression sets minute, hour, day of month, month, and day of week before the command.

Before switching to this setup, confirm that the scheduled request works and that the server can reach the site. Only then consider adding this line to wp-config.php:

define( 'DISABLE_WP_CRON', true );Code language: JavaScript (javascript)

Do not define DISABLE_WP_CRON without replacing the page-load trigger. Otherwise, due events can remain in the queue indefinitely.

WP-Cron troubleshooting checklist

The event is not listed

  • Confirm that the plugin activation hook ran after the code was added.
  • Check the exact hook name for spelling and prefixes.
  • Confirm that wp_next_scheduled() is checking the same arguments used during scheduling.
  • Deactivate and reactivate the plugin on a staging site to test the activation path.

The event exists but does not run

  • Run the named hook with WP-CLI and inspect the command output and callback result.
  • Check whether loopback requests, authentication, a firewall, or a maintenance mode plugin blocks wp-cron.php.
  • Check PHP error logs and the callback’s own logging.
  • Look for a fatal error or timeout inside the callback.
  • Verify that the site’s traffic pattern is sufficient for normal WP-Cron triggering.

The event runs more than once

  • Search the codebase for every call to wp_schedule_event().
  • Ensure that the wp_next_scheduled() guard runs before scheduling.
  • Check for duplicate copies of the plugin or a second callback using the same hook.
  • Make the callback idempotent so a duplicate request does not corrupt data.

Final thoughts

wp_schedule_event() is the main tool for recurring WordPress tasks, but reliable scheduling requires more than one function call. Register the callback, avoid duplicates, clean up during deactivation, use custom intervals carefully, and verify both the event and its callback.

For ordinary site maintenance, WP-Cron is often enough. For low-traffic sites or jobs that must run at a predictable time, a server-level scheduler can provide a more dependable trigger after it has been tested.

References

  1. Understanding WP-Cron Scheduling: WordPress Plugin Handbook
  2. Scheduling WP Cron Events: WordPress Plugin Handbook
  3. wp_schedule_event(): WordPress Code Reference
  4. wp_next_scheduled(): WordPress Code Reference
  5. wp_clear_scheduled_hook(): WordPress Code Reference
  6. Hooking WP-Cron Into the System Task Scheduler: WordPress Plugin Handbook
  7. wp_timezone(): WordPress Code Reference
  8. wp cron event list: WP-CLI Command
  9. wp cron event run: WP-CLI Command
  10. WP Crontrol: WordPress.org Plugin Directory
  11. cron_schedules: WordPress Hook Reference

Don’t forget to join our crash course on speeding up your WordPress site. Learn more below:

 
Yay! 🎉 You made it to the end of the article!
WPShout Editorial
Share:

2 Comments
Most Voted
Newest Oldest
dkolarevic
January 30, 2024 2:10 am

strtotime( ‘tomorrow 3am’) works for me instead of strtotime( ‘3am tomorrow’)!!

Blaz K.
March 9, 2020 5:56 am

Hi David,

nice post about CRON in WordPress but there is a mistake in “MAKING WP_SCHEDULE_EVENT DAILY RUN YOUR FUNCTION” section. You are saying that the code in the example will make wpshout_do_thing function run daily, which is not the case. wpshout_do_thing is in that case a hook as the third parameter of wp_schedule_event function is not a function but a hook. So, the example will not work. Unless, you add the following:

add_action(‘wpshout_do_thing’, ‘wpshout_do_thing_run’);

And then you also need a function wpshout_do_thing_run which does what you want it to do once daily.

You could also just add:

add_action(‘wpshout_do_thing’, ‘wpshout_do_thing’);

Then wpshout_do_thing function will run daily.

Regards,
Blaz