WordPress Cron Event Example: Schedule Plugin Tasks Safely
Schedule recurring WordPress plugin tasks safely with custom cron intervals, duplicate prevention, runtime locks, cleanup, and WP-CLI testing.
Published
April 21, 2026
Reading Time
3 min read
Updated
April 21, 2026

Hardening Notes
Baselines, access reduction, and default settings that stand up in production.
Best For
Teams preparing, launching, or maintaining WordPress as a backend service in a production stack.
Primary Topics
Editorial Focus
Control Ledger: Baselines, access reduction, and default settings that stand up in production. Updated on April 21, 2026.
Full Report
Last reviewed: April 21, 2026
WP-Cron is often blamed for unreliable background work, but many failures come from plugin code that schedules events incorrectly. The most common bug is scheduling the same event repeatedly on page load until thousands of duplicate jobs exist.
This article shows how to schedule a recurring plugin task safely, add a custom interval, prevent duplicate events, and clean up on deactivation.
Best fit
Use WP-Cron for lightweight maintenance tasks that can run shortly after their scheduled time: refreshing cached data, cleaning old logs, syncing a small feed, or sending a low-volume report. Do not use it as a precise job scheduler for high-volume queues or time-critical billing workflows.
Register a custom interval
<?php
/**
* Plugin Name: VulnWP Scheduled Sync
* Description: Example recurring plugin task with WP-Cron.
* Version: 1.0.0
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
add_filter( 'cron_schedules', 'vulnwp_add_sync_interval' );
function vulnwp_add_sync_interval( $schedules ) {
$schedules['vulnwp_every_fifteen_minutes'] = array(
'interval' => 15 * MINUTE_IN_SECONDS,
'display' => __( 'Every 15 minutes', 'vulnwp-cron' ),
);
return $schedules;
}
Schedule the event once
const VULNWP_SYNC_HOOK = 'vulnwp_run_scheduled_sync';
register_activation_hook( __FILE__, 'vulnwp_schedule_sync_event' );
register_deactivation_hook( __FILE__, 'vulnwp_unschedule_sync_event' );
add_action( VULNWP_SYNC_HOOK, 'vulnwp_run_scheduled_sync' );
function vulnwp_schedule_sync_event() {
if ( ! wp_next_scheduled( VULNWP_SYNC_HOOK ) ) {
wp_schedule_event(
time() + MINUTE_IN_SECONDS,
'vulnwp_every_fifteen_minutes',
VULNWP_SYNC_HOOK
);
}
}
function vulnwp_run_scheduled_sync() {
// Fetch, clean, cache, or sync a small unit of work.
update_option( 'vulnwp_last_sync_at', current_time( 'mysql', true ) );
}
The wp_next_scheduled() check prevents duplicate scheduling. Do not schedule events unconditionally on every request.
Unschedule on deactivation
function vulnwp_unschedule_sync_event() {
$timestamp = wp_next_scheduled( VULNWP_SYNC_HOOK );
if ( $timestamp ) {
wp_unschedule_event( $timestamp, VULNWP_SYNC_HOOK );
}
}
Cleanup matters because WordPress can continue trying to execute a hook after the plugin code is no longer active. That creates noise, errors, and support confusion.
Test with WP-CLI
wp cron event list --fields=hook,next_run,recurrence
wp cron event run vulnwp_run_scheduled_sync
wp option get vulnwp_last_sync_at
Manual execution is useful before deployment. It proves that the callback runs independently from normal page traffic.
Add a simple runtime lock
WP-Cron can overlap when a task takes longer than expected or when traffic triggers several requests around the same time. Use a short lock for jobs that should not run concurrently.
function vulnwp_run_scheduled_sync() {
if ( get_transient( 'vulnwp_sync_lock' ) ) {
return;
}
set_transient( 'vulnwp_sync_lock', 1, 10 * MINUTE_IN_SECONDS );
try {
update_option( 'vulnwp_last_sync_at', current_time( 'mysql', true ) );
} finally {
delete_transient( 'vulnwp_sync_lock' );
}
}
The lock should expire automatically in case PHP dies before cleanup. Keep the expiration longer than the expected job duration but short enough that a failed run does not block the feature all day.
Failure handling
Cron callbacks should fail quietly for visitors but visibly for operators. Store the last error message or timestamp in an option, send a safe admin notice, or expose the problem through a custom Site Health check. Do not echo output from cron callbacks.
Production checklist
- Register custom schedules before scheduling events that use them.
- Use
wp_next_scheduled()to prevent duplicates. - Keep cron callbacks small and idempotent.
- Log failures without exposing secrets.
- Unschedule plugin-owned events on deactivation.
- Use system cron to trigger WP-Cron on serious production sites.
Common mistakes
- Scheduling on every page load. This creates duplicate jobs.
- Using custom intervals before registering them. WordPress will reject unknown schedules.
- Doing too much work in one event. Split heavy work into smaller units.
- No deactivation cleanup. Stale hooks keep failing after plugin removal.
- Expecting exact timing. WP-Cron depends on visits unless system cron triggers it.
Related reading
This implementation guide pairs with our WP-Cron reliability checklist and the plugin uninstall cleanup example.


