Back to Blog
WordPress18 min read

WordPress Cron Jobs: The Complete WP-Cron Guide

WordPress has a built-in task scheduler called WP-Cron. It handles everything from publishing scheduled posts to checking for plugin updates. The problem? It only runs when someone visits your site. On low-traffic sites, scheduled tasks can be delayed by hours or missed entirely. This guide explains exactly how WP-Cron works, why it fails, and how to replace it with a real cron job that runs on time, every time.

What Is WP-Cron?

WP-Cron is WordPress's built-in pseudo-cron system. Unlike a traditional cron job that runs on the operating system's clock, WP-Cron is triggered by page visits. Every time someone loads a page on your WordPress site, WordPress checks whether any scheduled tasks are due to run. If they are, it fires them off in the background.

This design was an intentional choice by the WordPress core team. Since WordPress is designed to run on any hosting environment, including shared hosting where users have no access to the system crontab, WP-Cron provides a universal scheduling mechanism that works everywhere WordPress can be installed. No server configuration needed, no SSH access required, no hosting panel settings to configure.

The code lives in wp-cron.php in your WordPress root directory and in wp-includes/cron.php which contains the scheduling API functions. The system has been part of WordPress since version 2.1 (released in 2007) and has remained fundamentally unchanged since then.

How WP-Cron Works Internally

Understanding the internal mechanics of WP-Cron is essential for diagnosing problems. Here is what happens on every single page load of your WordPress site:

Step 1: The Check

When WordPress loads, it calls wp_cron() during the init action hook. This function checks the cron option in the wp_options table to see if any events are due. If nothing is scheduled, the function returns immediately with minimal overhead.

Step 2: The Loopback Request

If tasks are due, WordPress makes an asynchronous HTTP request (a "loopback") to wp-cron.php on the same site. This is a non-blocking request, meaning the visitor's page load is not delayed while the cron tasks execute. WordPress sets a timeout of 0.01 seconds for this request — it fires and forgets.

// Simplified version of WordPress's loopback mechanism
wp_remote_post( site_url( 'wp-cron.php' ), array(
    'timeout'   => 0.01,
    'blocking'  => false,
    'sslverify' => apply_filters( 'https_local_ssl_verify', false ),
) );

Step 3: The Lock

To prevent multiple visitors from triggering the same cron run simultaneously, WordPress uses a transient lock called doing_cron. When wp-cron.php starts executing, it sets this transient to the current timestamp. If another page load tries to trigger WP-Cron within the same window, it sees the lock and skips the cron run. The lock expires after 60 seconds by default (controlled by the WP_CRON_LOCK_TIMEOUT constant).

Step 4: Task Execution

Inside wp-cron.php, WordPress iterates through all due events in the cron array, fires each one by calling its registered callback function via do_action_ref_array(), and then reschedules recurring events for their next run. All scheduled events and their timestamps are stored as a serialized array in the cron row of the wp_options table.

-- View all scheduled WP-Cron events in the database
SELECT option_value FROM wp_options WHERE option_name = 'cron';

The output is a large serialized PHP array. Each key is a Unix timestamp, and the value contains the hook name, schedule, arguments, and interval for that event.

What Tasks Use WP-Cron

WP-Cron is deeply embedded in WordPress core and used by almost every major plugin. Here are the most important tasks that rely on it:

WordPress Core Tasks

  • Scheduled post publishing. When you schedule a post for a future date, WordPress stores the publish event in WP-Cron. This is the most user-visible cron task — and the most common failure point on low-traffic sites.
  • Core, plugin, and theme update checks. WordPress checks for available updates every 12 hours via the wp_update_plugins and wp_update_themes hooks.
  • Transient cleanup. Expired transients are deleted via the delete_expired_transients hook, which runs daily.
  • Database optimization. Some maintenance routines like revision cleanup and comment trash purging are cron-based.
  • Email notifications. Digest emails, comment moderation notices, and auto-update result emails all use WP-Cron.

Popular Plugin Tasks

  • WooCommerce: Order status processing, abandoned cart cleanup, scheduled sales start/end, stock status syncing, analytics aggregation, and webhook delivery queue processing.
  • Backup plugins (UpdraftPlus, BackWPup): Scheduled backups of files and database. A missed cron run means a missed backup.
  • SEO plugins (Yoast, Rank Math): Sitemap regeneration, SEO data indexing, and schema cache updates.
  • Cache plugins (W3 Total Cache, WP Super Cache): Cache garbage collection and preloading/warming routines.
  • Email marketing (Mailchimp, Newsletter): Subscriber sync, campaign scheduling, and batch email processing.

WP-Cron vs Real Cron

The fundamental difference between WP-Cron and a real cron job is the trigger mechanism. Here is a detailed comparison:

FeatureWP-CronReal Cron (system/external)
TriggerPage visit by a userSystem clock / external service
AccuracyDepends on traffic volumeExact to the minute (or second)
Traffic dependencyCompletely dependentIndependent of traffic
Page load impactAdds overhead to every requestZero impact on visitors
Setup requiredNone (built into WordPress)Server access or external service
MonitoringNone by defaultLogs, alerts, dashboards
Concurrent runsLock mechanism (can still overlap)Controlled by system/service
Works on shared hostingYes (always)External service: yes. System cron: depends on host

For a deeper understanding of cron expressions and scheduling syntax, our glossary has a detailed breakdown with examples.

Why WP-Cron Is Unreliable

WP-Cron is not broken by design — it works exactly as intended. The problem is that its design makes assumptions that do not hold true for a large percentage of WordPress sites. Here are the specific failure modes:

1. Traffic Dependency

This is the fundamental flaw. If no one visits your site for 8 hours, no cron tasks run for 8 hours. A post scheduled for 9:00 AM will not publish until someone actually loads a page. For sites with under 100 daily visitors, this is a regular occurrence. Even popular sites have quiet periods (typically 2-6 AM in their timezone) where WP-Cron tasks pile up.

2. Missed Schedules

When multiple tasks pile up during a no-traffic window, WordPress tries to run them all at once when the next visitor arrives. This can cause timeouts, especially on shared hosting with tight PHP execution limits (30 seconds is common). When a timeout occurs, some tasks execute and others are lost. WordPress marks posts as "Missed schedule" — a status message that has frustrated millions of users.

3. Performance Overhead on Every Page Load

Even when no tasks are due, WordPress must query the wp_options table and deserialize the cron array on every single page load to check if anything needs to run. On sites with hundreds of scheduled events (common with WooCommerce and multiple plugins), this adds measurable overhead. When tasks do need to run, the loopback HTTP request adds additional load to the server, affecting the page speed of the visitor who triggered it.

4. Loopback Failures

The loopback request (WordPress calling its own wp-cron.php URL) fails on a surprising number of hosting setups. Common causes include:

  • HTTP basic authentication on staging/development sites blocking the request
  • SSL certificate issues causing the HTTPS loopback to fail
  • Firewall or security plugin blocking the self-referential HTTP request
  • DNS resolution issues where the server cannot resolve its own domain name
  • Server-level cURL restrictions or disabled functions on shared hosting

WordPress offers no notification when the loopback fails. The Site Health tool (Tools > Site Health) includes a loopback test, but many site owners never check it.

5. No Monitoring or Alerting

WP-Cron has zero built-in monitoring. There is no log of when tasks ran, whether they succeeded, or how long they took. If a scheduled backup fails to run for a week, WordPress will not tell you. You will only discover the problem when you need that backup and it does not exist. For comparison, a proper health check system alerts you immediately when a scheduled task fails.

How to Disable WP-Cron

The first step in replacing WP-Cron with a real cron job is to disable the default behavior. This is a one-line change in your wp-config.php file:

// Add this line to wp-config.php
// BEFORE the line: "That's all, stop editing!"
define( 'DISABLE_WP_CRON', true );

What this does:

  • Stops WordPress from checking for and triggering cron tasks on every page load
  • Eliminates the loopback HTTP request overhead
  • Does NOT delete any scheduled events — they remain in the database
  • Does NOT break any plugins — they can still register cron events

Important

After disabling WP-Cron, you must set up an external trigger, otherwise no scheduled tasks will run at all. Do not disable WP-Cron until you have your replacement ready.

There is also a lesser-known constant you can use to change the cron lock timeout instead of fully disabling WP-Cron:

// Optional: Change the cron lock timeout (default is 60 seconds)
define( 'WP_CRON_LOCK_TIMEOUT', 120 );

// Optional: Set an alternate cron URL (useful for multisite)
define( 'ALTERNATE_WP_CRON', true );

ALTERNATE_WP_CRON uses a redirect-based approach instead of the loopback request. It can fix cron issues on some problematic hosting environments, but it is not a substitute for a real external cron trigger.

Setting Up CronJobPro as WordPress Cron

Using an external cron service is the most reliable way to trigger WordPress scheduled tasks. CronJobPro sends an HTTP request to your wp-cron.php endpoint on a precise schedule, regardless of whether anyone is visiting your site. Here is how to set it up:

Step 1: Disable WP-Cron

Add define( 'DISABLE_WP_CRON', true ); to your wp-config.php as described above.

Step 2: Create a CronJobPro Account

Sign up at cronjobpro.com/signup. The free plan includes 5 jobs, which is more than enough for WordPress cron.

Step 3: Create a New Job

In the CronJobPro dashboard, create a new job with these settings:

URLhttps://yoursite.com/wp-cron.php
MethodGET
Schedule*/5 * * * * (every 5 minutes)
TimezoneMatch your WordPress timezone (Settings > General)
NotificationsEnable failure alerts (email or webhook)

Use our cron expression generator to build the exact schedule you need. For most WordPress sites, every 5 minutes (*/5 * * * *) is ideal. WooCommerce stores with time-sensitive order processing may benefit from every 1-2 minutes.

Step 4: Verify It Works

After saving the job, trigger a manual run from the CronJobPro dashboard. You should see a successful response (HTTP 200). Then check your WordPress site — scheduled posts should publish on time, and plugins should process their queued tasks correctly.

CronJobPro provides a full execution history with response codes, response times, and response body for every run. If something goes wrong, you can see exactly what happened. This is a massive improvement over WP-Cron's zero-visibility approach. Learn more about how to monitor cron jobs effectively.

System Crontab Alternative

If you have SSH access to your server, you can use the system crontab to trigger wp-cron.php. This is a viable alternative if you prefer not to use an external service, though you lose the monitoring and alerting benefits. Here are three approaches:

Option A: Using wget

# Open the crontab editor
crontab -e

# Add this line to trigger wp-cron.php every 5 minutes
*/5 * * * * /usr/bin/wget -q -O /dev/null https://yoursite.com/wp-cron.php

Option B: Using curl

# Trigger via curl (more common on modern systems)
*/5 * * * * /usr/bin/curl -s https://yoursite.com/wp-cron.php > /dev/null 2>&1

Option C: Using PHP Directly

# Run wp-cron.php directly via PHP (bypasses HTTP, faster)
*/5 * * * * cd /var/www/html && /usr/bin/php wp-cron.php > /dev/null 2>&1

The PHP direct method is faster because it skips the HTTP overhead. However, it requires the crontab user to have file system access to your WordPress installation. The wget/curl methods work regardless of file permissions because they go through the web server.

For detailed cron syntax help, see our cron expression cheatsheet which covers all special characters, ranges, and common patterns.

Tip

The downside of system crontab is that if your server goes down, the cron stops too. An external service like CronJobPro will still attempt to reach your endpoint and alert you when it fails, giving you early warning of server issues. See our guide on website monitoring with cron jobs.

WP-CLI Cron Commands

WP-CLI is the official command-line tool for WordPress. It provides powerful commands for inspecting and managing WP-Cron events. If you have SSH access to your server, WP-CLI is invaluable for debugging cron issues.

# List all scheduled cron events
wp cron event list

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

# Run a specific cron event by hook name
wp cron event run wp_update_plugins

# Test if WP-Cron is working correctly
wp cron test

# List all registered cron schedules (hourly, daily, etc.)
wp cron schedule list

# Delete a specific cron event
wp cron event delete <hook>

# Show the next scheduled run for all events
wp cron event list --fields=hook,next_run_relative,recurrence

You can also use WP-CLI as the cron trigger itself. This is often the cleanest approach because it runs directly in the WordPress context without HTTP overhead:

# Add to system crontab: run all due WP-Cron events every 5 minutes
*/5 * * * * cd /var/www/html && /usr/local/bin/wp cron event run --due-now > /dev/null 2>&1

The wp cron event list command is particularly useful for auditing what plugins are doing with WP-Cron. It is not uncommon to find plugins that register events but never clean them up, leading to a bloated cron array with hundreds of stale entries.

Popular WordPress Cron Plugins

These plugins provide a graphical interface for viewing and managing WP-Cron events directly from the WordPress admin dashboard. They are useful for debugging but do not solve the fundamental reliability problem — for that, you still need an external trigger.

WP Crontrol

With 200,000+ active installations, WP Crontrol is the most popular cron management plugin. It adds a "Cron Events" page under the Tools menu where you can:

  • View all scheduled cron events with their next run time and recurrence
  • Manually run any cron event immediately
  • Edit, delete, or add new cron events
  • Add custom cron schedules (e.g., every 5 minutes, weekly)
  • See warnings for events with missing callbacks (orphaned events)
// Example: Register a custom cron schedule via PHP (functions.php)
add_filter( 'cron_schedules', function( $schedules ) {
    $schedules['every_five_minutes'] = array(
        'interval' => 300,
        'display'  => 'Every 5 Minutes',
    );
    return $schedules;
} );

Advanced Cron Manager

Advanced Cron Manager offers similar features with a more modern interface and additional capabilities like cron event execution logging, event pausing without deletion, and bulk management. It is particularly useful on WooCommerce stores where the cron event list can grow to hundreds of entries.

Both plugins are excellent for inspecting WP-Cron, but neither one fixes the underlying trigger problem. They show you what is scheduled — you still need an external service or system cron to make sure those events actually fire on time.

Best Practices

For Site Owners

  • Always disable WP-Cron and use an external trigger. This is the single most impactful WordPress performance optimization many site owners overlook. It eliminates unnecessary HTTP overhead on every page load and ensures tasks run on time.
  • Choose the right frequency. Every 5 minutes works for most sites. WooCommerce stores should consider every 1-2 minutes. Personal blogs with no time-sensitive tasks can use every 15 minutes.
  • Monitor your cron trigger. Use a service that alerts you when the cron request fails. A failing cron trigger can silently break backups, updates, email delivery, and more.
  • Audit your cron events periodically. Install WP Crontrol and check for orphaned events (events whose plugin was deactivated but left behind). These waste execution time and can cause errors.
  • Match timezones. Your WordPress timezone (Settings > General) and your external cron service timezone should match to avoid confusion when debugging. WP-Cron internally uses UTC, but displays times in your configured timezone.

For Developers

  • Use the WordPress Cron API correctly. Always check if an event is already scheduled before adding it with wp_next_scheduled() before calling wp_schedule_event(). Otherwise, you risk creating duplicate events.
  • Clean up on deactivation. Use wp_clear_scheduled_hook() in your plugin's deactivation hook to remove all scheduled events. Orphaned cron events are one of the most common WordPress "junk" issues.
  • Keep cron callbacks fast. Each cron execution shares the PHP process time limit. If your callback takes 25 seconds of a 30-second limit, other scheduled tasks may not get to run. For long-running tasks, break them into batches.
  • Do not assume exact timing. WP-Cron events may run seconds, minutes, or hours late depending on the setup. Design your callbacks to be idempotent — safe to run multiple times without side effects.
// Correct pattern: schedule event on activation, clean up on deactivation

// In your plugin's activation hook
register_activation_hook( __FILE__, function() {
    if ( ! wp_next_scheduled( 'my_plugin_daily_task' ) ) {
        wp_schedule_event( time(), 'daily', 'my_plugin_daily_task' );
    }
} );

// Register the callback
add_action( 'my_plugin_daily_task', 'my_plugin_do_daily_work' );
function my_plugin_do_daily_work() {
    // Your task logic here
}

// Clean up on deactivation
register_deactivation_hook( __FILE__, function() {
    wp_clear_scheduled_hook( 'my_plugin_daily_task' );
} );

Troubleshooting

Here are the most common WP-Cron problems and their solutions. If you are having trouble with a system-level cron job (not WordPress-specific), see our dedicated cron job not running troubleshooting guide.

Scheduled Posts Show "Missed Schedule"

This is the most visible WP-Cron failure. WordPress displays "Missed schedule" in red next to the post title when a scheduled post was not published at its target time.

Cause: No page visit occurred at the scheduled time to trigger WP-Cron, or WP-Cron was disabled without setting up an external trigger.

Fix: Set up an external cron job to call wp-cron.php every 1-5 minutes. For missed posts that are stuck, you can manually publish them or use WP-CLI:

# Publish all missed scheduled posts
wp cron event run --due-now

# Or publish a specific post
wp post update <post_id> --post_status=publish

WP-Cron Not Running At All

Symptoms: No scheduled tasks execute. Plugin data stops updating. Backups stop running.

Diagnostic steps:

# 1. Check if WP-Cron is disabled
wp config get DISABLE_WP_CRON

# 2. Test the loopback request
wp cron test

# 3. Try calling wp-cron.php directly
curl -I https://yoursite.com/wp-cron.php

# 4. Check for error responses
curl -v https://yoursite.com/wp-cron.php 2>&1 | head -30

# 5. Verify the cron array is not corrupted
wp option get cron --format=json | python3 -m json.tool | head -50

Duplicate Cron Events

Symptoms: The same task runs multiple times. You see dozens of identical events in WP Crontrol. This is often caused by plugins that call wp_schedule_event() without first checking wp_next_scheduled().

# List all events to find duplicates
wp cron event list --fields=hook,next_run,recurrence

# Count events per hook to identify duplicates
wp cron event list --format=json | python3 -c "
import json, sys, collections
events = json.load(sys.stdin)
counts = collections.Counter(e['hook'] for e in events)
for hook, count in counts.most_common(20):
    print(f'{count:4d}  {hook}')
"

# Delete all instances of a specific duplicate hook
wp cron event delete <hook_name>

WP-Cron Uses Too Much Server Resources

Symptoms: Spikes in server CPU or memory correlated with visitor traffic. The wp-cron.php process appears in server logs consuming significant resources.

Fix: This almost always means WP-Cron is still triggering on page load (not properly disabled) and running heavy tasks. Disable WP-Cron and use an external trigger to move the workload off visitor-facing requests. Also audit your cron events — deactivate or optimize any that take more than a few seconds to complete.

Frequently Asked Questions

What is WP-Cron and how does it differ from a real cron job?

WP-Cron is WordPress's built-in task scheduler. Unlike a real system cron job that runs on a fixed clock schedule via the operating system, WP-Cron is a "pseudo-cron" that only checks for and executes scheduled tasks when someone visits your website. This means tasks can be delayed or missed entirely on low-traffic sites. A real cron job runs on the operating system's clock and fires at exactly the time you specify, regardless of website traffic.

Why are my WordPress scheduled posts not publishing on time?

WordPress uses WP-Cron to publish scheduled posts, and WP-Cron only runs when someone visits your site. If no one visits during the scheduled publish time, the post will not publish until the next visit. The fix is to disable WP-Cron and use an external cron service like CronJobPro to trigger wp-cron.php every few minutes. This ensures posts publish within minutes of their scheduled time, even on low-traffic sites.

How do I disable WP-Cron in WordPress?

Add the line define( 'DISABLE_WP_CRON', true ); to your wp-config.php file, before the line that says "That's all, stop editing!" This prevents WordPress from running WP-Cron on every page load. You then need to set up an external cron job to call wp-cron.php on a regular schedule to ensure scheduled tasks still execute.

How often should I trigger wp-cron.php externally?

For most WordPress sites, every 5 minutes provides a good balance between timeliness and server load. High-traffic WooCommerce stores or sites with time-sensitive tasks (like auction plugins or appointment scheduling) may benefit from every 1-2 minutes. Sites with minimal scheduled tasks (personal blogs, small business sites) can safely use every 15 minutes. The key is that even a 15-minute interval is vastly more reliable than WP-Cron's traffic-dependent approach.

Does disabling WP-Cron break WordPress plugins?

No. Disabling WP-Cron with DISABLE_WP_CRON only stops WordPress from checking for scheduled tasks on every page load. All plugins that use the WordPress scheduling API (wp_schedule_event, wp_cron) will continue to work normally as long as you set up an external cron job to call wp-cron.php regularly. In fact, plugins will work more reliably because the external trigger runs on a fixed schedule rather than depending on random visitor traffic.

Related Articles

Stop relying on WP-Cron

Replace WordPress's unreliable pseudo-cron with a real scheduled trigger. CronJobPro calls your wp-cron.php on a precise schedule, monitors every execution, and alerts you when something fails. Free for up to 5 jobs.