iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

wp-cron

WordPress’s built-in cron (wp-cron) runs scheduled tasks — sending emails, fetching feeds, cleaning expired data. It’s NOT a real cron; it fires on page loads. For reliable scheduling, disable it and run via real cron + WP-CLI.

wp_schedule_event, real cron, debugging

EXAMPLE
<?php
// 1) Schedule a recurring event
add_action( 'init', function () {
    if ( ! wp_next_scheduled( 'my_daily_cleanup' ) ) {
        wp_schedule_event( time(), 'daily', 'my_daily_cleanup' );
    }
} );

// Hook the actual work
add_action( 'my_daily_cleanup', function () {
    global $wpdb;
    $deleted = $wpdb->query( "DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_old_%' AND option_value < UNIX_TIMESTAMP()" );
    error_log( "Cleaned {$deleted} expired transients" );
} );

// 2) Built-in schedules
// • hourly      — every hour
// • twicedaily  — every 12 hours
// • daily       — every 24 hours
// • weekly      — every 7 days  (since WP 5.4)

// 3) Custom interval
add_filter( 'cron_schedules', function ( $schedules ) {
    $schedules['every_15_min'] = array(
        'interval' => 15 * MINUTE_IN_SECONDS,
        'display'  => 'Every 15 minutes',
    );
    $schedules['every_5_min'] = array(
        'interval' => 5 * MINUTE_IN_SECONDS,
        'display'  => 'Every 5 minutes',
    );
    return $schedules;
} );

// Now you can use 'every_15_min' in wp_schedule_event.

// 4) One-off scheduled event
wp_schedule_single_event( time() + HOUR_IN_SECONDS, 'send_followup_email', array( $user_id ) );

add_action( 'send_followup_email', function ( $user_id ) {
    $user = get_userdata( $user_id );
    wp_mail( $user->user_email, 'Welcome back', '…' );
} );

// 5) Pass arguments
wp_schedule_event( time(), 'daily', 'sync_with_api', array( 'us-east' ) );

add_action( 'sync_with_api', function ( $region ) {
    // perform sync for region
} );

// 6) Unschedule
$timestamp = wp_next_scheduled( 'my_daily_cleanup' );
if ( $timestamp ) {
    wp_unschedule_event( $timestamp, 'my_daily_cleanup' );
}

// Unschedule with arguments (must match exactly)
wp_clear_scheduled_hook( 'sync_with_api', array( 'us-east' ) );

// Cleanup on plugin deactivation
register_deactivation_hook( __FILE__, function () {
    wp_clear_scheduled_hook( 'my_daily_cleanup' );
    wp_clear_scheduled_hook( 'sync_with_api' );
} );

// 7) The wp-cron PROBLEM
// wp-cron fires on PAGE LOADS. If your site has no traffic, scheduled tasks NEVER RUN.
// Even with traffic, timing is imprecise (can fire late or repeatedly).
// On high-traffic sites it can run per request, slowing down responses.

// 8) Disable wp-cron + use real cron — recommended for production
// wp-config.php
define( 'DISABLE_WP_CRON', true );

// Then add a real cron job (Linux server crontab):
// crontab -e
//   */5 * * * * curl -s 'https://example.com/wp-cron.php?doing_wp_cron' > /dev/null 2>&1

// Or with WP-CLI (preferred — no HTTP request needed):
//   */5 * * * * cd /var/www/html && wp cron event run --due-now > /dev/null 2>&1

// 9) WP-CLI commands
wp cron event list                                    # see all scheduled events
wp cron event run my_daily_cleanup                    # fire NOW
wp cron event run --due-now                            # fire all due events
wp cron event delete my_daily_cleanup
wp cron event schedule my_daily_cleanup now daily
wp cron schedule list                                  # available schedules
wp cron test                                            # check if wp-cron is reachable

// 10) Debug — what's scheduled?
// Install WP Crontrol plugin → Tools → Cron Events
// Or programmatically:
$crons = _get_cron_array();
foreach ( $crons as $timestamp => $cron ) {
    foreach ( $cron as $hook => $events ) {
        echo $hook . ' next at ' . wp_date( 'Y-m-d H:i:s', $timestamp ) . "\n";
    }
}

// 11) Error handling + logging
add_action( 'my_task', function () {
    try {
        $result = expensive_operation();
        update_option( 'my_task_last_run', time() );
    } catch ( \Throwable $e ) {
        error_log( 'my_task failed: ' . $e->getMessage() );
        if ( function_exists( 'wp_mail' ) ) {
            wp_mail( get_option( 'admin_email' ), 'Cron failure', $e->getMessage() );
        }
    }
} );

// 12) Timeout + memory
// wp-cron requests can be killed by PHP max_execution_time (default 30s).
// For long jobs:
//   • Use Action Scheduler (used by WooCommerce; better at scale)
//   • Chunk: schedule next single event from inside the handler
//   • Increase PHP timeout in cron config

// 13) Action Scheduler — robust alternative
// composer require woocommerce/action-scheduler
as_schedule_recurring_action(
    time(),                                    // first run
    HOUR_IN_SECONDS,                            // interval
    'my_recurring_job',
    array( /* args */ ),
    'my_group',
);

// • Stored in database; survives missed runs
// • Batch processing built in
// • Dashboard in WP admin

// 14) Common bugs
// • Forgot to check wp_next_scheduled → events scheduled MULTIPLE TIMES on every load
// • Hook on 'init' so cron registration on every request → fine + idempotent if wp_next_scheduled used
// • Unschedule with mismatched args → cleared the wrong event; pass exact args
// • Calling expensive code in a request-handling cron event → slow page loads; use real cron
// • DISABLE_WP_CRON without setting up real cron → events NEVER fire
// • Plugin deactivated but events linger → register_deactivation_hook
// • Args not serialisable → fatal error scheduling
// • Job runs but next iteration overlaps → use lock (transient or option)
// • Forgetting timezone — schedule with UTC timestamp
// • Errors in cron handler silently swallowed — always log

Why it matters

WP-cron runs on page loads, not on a real schedule. For anything important, define(\"DISABLE_WP_CRON\", true) and run a real cron entry pointing at wp cron event run --due-now. Always guard wp_schedule_event with wp_next_scheduled to avoid duplicates, register a deactivation cleanup, and reach for Action Scheduler when you need reliable, batch-processed jobs at scale.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
if (!wp_next_scheduled('my_cron')) {
    wp_schedule_event(time(), 'hourly', 'my_cron');
}
add_action('my_cron', 'do_work');
Try it Yourself »

Discussion

Loading…