A WordPress cron job is a scheduled task that runs automatically in the background β things like checking for plugin updates, sending scheduled emails, publishing scheduled posts, and clearing expired transients. Unlike a real server cron job, WordPress cron (called WP-Cron) doesn’t run on a fixed schedule. Instead, it triggers on page visits: every time someone loads a page on your site, WordPress checks whether any scheduled tasks are due and runs them.
This visitor-triggered design means WP-Cron doesn’t run on low-traffic sites β if nobody visits your site at 3am, your 3am scheduled task won’t fire. For reliable task scheduling on WordPress, the solution is to disable WP-Cron and replace it with a real server-side cron job.
WordPress Cron Jobs β Everything in One Place
- Triggers on page visits β not on a real time schedule
- Lives in
wp-cron.phpin your WordPress root - Runs tasks registered by WordPress core, themes, and plugins
- Unreliable on low-traffic sites β tasks fire late or not at all
- Can cause performance spikes on high-traffic sites
- Scheduled posts don’t publish on time
- Backup plugins miss scheduled runs
- WooCommerce order emails send late
- Plugin update checks stop running
- Expired transients accumulate in the database
WordPress cron jobs are one of those topics where the gap between how they’re supposed to work and how they actually work on most sites is large enough to cause real problems β missed backups, delayed emails, scheduled posts that never go live, plugin update checks that silently stop running.
I’ve debugged WP-Cron on dozens of sites. The problems always trace back to the same root issue: most developers set up WordPress, leave WP-Cron on its default configuration, and assume it works like a real cron job. It doesn’t β and on any site that isn’t receiving constant traffic, it’s unreliable by design.
This guide covers what WP-Cron actually is, how to view every scheduled task currently registered on your site, how to diagnose when it stops working, how to replace it with a proper server cron job on every major host, and how to register your own custom cron events in WordPress.
How It WorksHow WordPress Cron Actually Works (And Why It’s Not a Real Cron)
A traditional server cron job is managed by the operating system. You tell the server to run a specific command at a specific time, and the server does it β regardless of traffic, user activity, or anything else. It is deterministic and reliable.
WP-Cron works completely differently. Here is what actually happens on every page load on a WordPress site:
- A visitor (or a bot, or Googlebot) loads any page on your site
- WordPress processes the request and, near the end of execution, calls
wp_cron() wp_cron()checks thecronoption in the WordPress database β a serialized array of all scheduled events and their next-run timestamps- If any events are overdue (their next-run timestamp is in the past), WordPress spawns a non-blocking HTTP request to
yourdomain.com/wp-cron.php wp-cron.phpruns the overdue events in the background- The visitor’s page loads normally β they don’t wait for the cron jobs to finish
The critical implication of step 1: if no page is loaded, wp-cron.php is never called. A scheduled post set to publish at 3:00 AM will publish whenever the next page visit happens after 3:00 AM β which on a low-traffic blog might be 3:47 AM, or 9:15 AM, or not until the next day.
What Tasks Are Registered by Default?
A fresh WordPress installation with a standard plugin stack registers roughly 15β30 cron events. Here are the core ones you’ll see on every site:
| Event Hook | Frequency | What It Does |
|---|---|---|
| wp_version_check | Twice daily | Checks WordPress.org for a newer WordPress version |
| wp_update_plugins | Twice daily | Checks all installed plugins for available updates |
| wp_update_themes | Twice daily | Checks all installed themes for available updates |
| wp_scheduled_delete | Daily | Permanently deletes posts in Trash older than 30 days |
| delete_expired_transients | Daily | Cleans up expired transients from the database |
| wp_privacy_delete_old_export_files | Hourly | Deletes old personal data export files (GDPR) |
| recovery_mode_clean_expired_keys | Daily | Removes expired recovery mode keys |
| publish_future_post | Per scheduled post | Publishes posts scheduled for a future date/time |
| wp_site_health_scheduled_check | Weekly | Runs background Site Health checks |
Every plugin you install can register its own cron events on top of these. WooCommerce alone registers 6β10 additional events. A backup plugin registers its scheduled backup. An email marketing plugin registers its queue processor. On a site with 25+ active plugins, you can easily have 40β60 registered cron events.
WP-Cron vs Real CronWP-Cron vs Real Server Cron: When to Use Each
| Feature | WP-Cron (default) | Real Server Cron |
|---|---|---|
| Runs on exact schedule | β Traffic-dependent | β Always on time |
| Works on low-traffic sites | β Unreliable | β Fully reliable |
| Performance impact on visitors | β Adds overhead to page loads | β Runs independently |
| Works on high-traffic sites | β Can fire too frequently | β Controlled frequency |
| Setup required | β None β works out of the box | β Requires server access |
| Works on all hosting types | β Yes | β Requires cPanel / SSH access |
| Suitable for critical tasks | β Backups, emails β risky | β Yes |
| Recommended for production sites | β No | β Yes |
How to View All Scheduled WordPress Cron Jobs
There’s no way to see your scheduled cron events in the standard WordPress admin interface. You need either WP-CLI or a plugin. Here are both methods:
If your host provides WP-CLI access (Cloudways, Kinsta, SiteGround, and most managed hosts do), this is the fastest way to see everything:
# List all scheduled cron events wp cron event list # List events with next run time and recurrence wp cron event list --fields=hook,next_run_relative,recurrence # Run a specific event manually (useful for testing) wp cron event run wp_update_plugins # See all registered cron schedules (hourly, daily, etc.) wp cron schedule list # Delete a specific cron event wp cron event delete hook_name
The --fields flag is useful for a clean overview. On a typical WordPress site, wp cron event list outputs 15β60 rows depending on your plugin stack.
WP Crontrol (free, 800k+ active installs) adds a Cron Events screen to your WordPress admin at Tools β Cron Events. It shows every scheduled event, its next run time, its recurrence, and the file/line that registered it.
Key things you can do in WP Crontrol:
- Run any cron event manually with one click β essential for testing whether an event actually works
- Delete orphaned cron events left behind by uninstalled plugins
- Add custom cron events without writing code
- See exactly which plugin registered each event β by filename and function name
- Edit the schedule of any existing event
TopTut WordPress Cron Manager
A cleaner, faster way to view, test, and fix all scheduled cron events on your WordPress site β directly from your dashboard. Built for developers who need more than WP Crontrol’s basic interface.
- Visual timeline of all scheduled events with overdue highlighting
- One-click manual trigger with execution time display
- Orphaned event detection (events left by uninstalled plugins)
- Stuck event alerts β events that are overdue by more than 2Γ their interval
- Export full cron schedule as CSV for client reporting
- Built-in guide for disabling WP-Cron and setting up a real server cron
Why WP-Cron Is Not Working: 5-Step Diagnostic
If your scheduled posts are publishing late, your backup plugin is missing runs, or WooCommerce emails are delayed β WP-Cron is likely the culprit. Work through this diagnostic in order:
wp-config.php and search for DISABLE_WP_CRON. If you see define('DISABLE_WP_CRON', true);, WP-Cron has been disabled β intentionally or by a previous developer β without a real server cron job set up to replace it. Either remove that line (re-enables WP-Cron) or set up a real cron job first (see the next section below), then leave the disable flag in place.
Fix Remove the line if no server cron exists, or add a real cron job then keep the disable flag.
Fix Contact your host to allow loopback requests, or switch to a real server cron job which bypasses this requirement entirely.
Fix Set up a real server cron job (see next section) to guarantee reliable execution regardless of traffic.
wp cron event list to find the specific event. Check its next run time β if it shows a timestamp in the far past (days or weeks overdue), the event is stuck. This can happen when a plugin registers an event but doesn’t update the timestamp after a failed run.
Fix In WP Crontrol, delete the stuck event and re-register it β or deactivate and reactivate the plugin that registered it, which typically re-registers its cron events fresh.
wp-cron.php and serve a cached response instead of executing the file. Check your caching plugin’s exclusions list β wp-cron.php should be excluded from caching. In WP Rocket: File Optimization β Never Cache URL β add /wp-cron.php. In Cloudflare: create a Page Rule for */wp-cron.php* β Cache Level: Bypass.
Fix Add
wp-cron.php to your caching exclusions at both the plugin level and the CDN level.
How to Disable WP-Cron and Set Up a Real Server Cron Job
This is the correct, permanent solution for any WordPress site where reliable task scheduling matters. The setup has two parts: disable WP-Cron in WordPress, then configure your server to call wp-cron.php on a fixed schedule instead.
Step 1: Disable WP-Cron in wp-config.php
Add this line to wp-config.php, immediately above the /* That's all, stop editing! */ line:
/* Disable WP-Cron β replaced by real server cron job */ define( 'DISABLE_WP_CRON', true ); /* That's all, stop editing! Happy publishing. */
This tells WordPress to stop spawning HTTP requests to wp-cron.php on every page load. Your scheduled tasks will not run until you complete Step 2 β so set up the server cron job before or immediately after adding this line.
Step 2: Set Up a Real Cron Job at Your Host
The cron job needs to call wp-cron.php via PHP directly (not via HTTP). The recommended frequency is every 5 minutes β this ensures hourly tasks fire within their expected window and prevents any task from being delayed more than 5 minutes. Here’s how to do it on the most common hosts:
- Log into cPanel β Advanced β Cron Jobs
- Set the frequency to Every 5 Minutes (or use the common settings dropdown)
- In the Command field, enter β replacing the path with your actual WordPress installation path:
php /home/yourusername/public_html/wp-cron.php
To find your exact path: in cPanel β File Manager, navigate to your WordPress root folder and note the path in the address bar. It typically follows the pattern /home/yourusername/public_html/.
If php alone doesn’t work, try the full PHP binary path:
/usr/local/bin/php /home/yourusername/public_html/wp-cron.php
- Log into the Dreamhost Panel β Goodies β Cron Jobs
- Click Add New Cron Job
- Set the user to your shell user
- Set When to run: Custom β enter
*/5 * * * *(every 5 minutes) - In the Command field:
/usr/local/php82/bin/php /home/yourusername/yourdomain.com/wp-cron.php
Note: Dreamhost uses versioned PHP binary paths. Replace php82 with your active PHP version (check in the Dreamhost panel under Managed PHP).
- Connect via SSH to your Cloudways server
- Run
crontab -eto open the crontab editor - Add this line β replace the path with your application’s public_html path:
# WordPress cron β runs every 5 minutes */5 * * * * /usr/bin/php /home/master/applications/yourapp/public_html/wp-cron.php
Find your exact application path in the Cloudways dashboard β Application β Application Management β Application Credentials.
Both Kinsta and WP Engine provide a cron job management interface in their dashboards β you don’t need SSH access.
Kinsta: MyKinsta β Sites β Your Site β Tools β Cron Jobs β Add Cron Job. Use the command:
cd /www/yoursitename/public && php wp-cron.php
WP Engine: WP Engine dashboard β Sites β Your Site β Cron Jobs. Add a new cron job with the above command and set the frequency to every 5 minutes.
On both managed hosts, WP-Cron is often already configured or managed automatically β check with their support before adding a custom cron job to avoid duplicate execution.
wp cron event list. The “Next Run” times should be updating β events that were previously overdue should now show future timestamps. If a scheduled post is due, verify it publishes within 5 minutes of its scheduled time.
How to Register a Custom WordPress Cron Job in Code
If you’re building a plugin or custom functionality that needs to run on a schedule, here’s the correct way to register a cron event in WordPress. There are two parts: scheduling the event, and hooking your function to it.
/** * Schedule a custom cron event on plugin/theme activation. * This registers the event only once β wp_next_scheduled prevents * duplicate registrations on subsequent page loads. */ function toptut_schedule_my_task() { if ( ! wp_next_scheduled( 'toptut_my_custom_event' ) ) { wp_schedule_event( time(), // Start time (Unix timestamp) 'hourly', // Recurrence: hourly, twicedaily, daily, weekly 'toptut_my_custom_event' // Hook name β must be unique ); } } add_action( 'wp', 'toptut_schedule_my_task' ); /** * Hook your actual function to the cron event. * This is what runs when the scheduled event fires. */ function toptut_my_task_callback() { // Your code here β e.g. send an email, process a queue, clean a table error_log( 'toptut_my_custom_event fired at: ' . current_time( 'mysql' ) ); } add_action( 'toptut_my_custom_event', 'toptut_my_task_callback' ); /** * Clean up on plugin/theme deactivation. * Always remove your cron events when your plugin is deactivated β * orphaned cron events accumulate in the database otherwise. */ function toptut_deactivate_my_task() { $timestamp = wp_next_scheduled( 'toptut_my_custom_event' ); wp_unschedule_event( $timestamp, 'toptut_my_custom_event' ); } register_deactivation_hook( __FILE__, 'toptut_deactivate_my_task' );
The three most common mistakes when registering custom cron events:
- Missing the wp_next_scheduled check β without it, every page load registers a new copy of the event, leading to hundreds of duplicate entries in the cron table
- Not cleaning up on deactivation β orphaned events from uninstalled plugins accumulate and waste execution time on every cron run
- Using a non-unique hook name β if two plugins register events with the same hook name, they will interfere with each other
Adding a Custom Cron Schedule (Custom Interval)
WordPress’s built-in schedules are hourly, twicedaily, daily, and weekly. If you need a different interval β every 5 minutes, every 30 minutes, every 6 hours β register a custom schedule:
function toptut_add_cron_intervals( $schedules ) { // Every 5 minutes $schedules['every_five_minutes'] = array( 'interval' => 5 * 60, 'display' => __( 'Every 5 Minutes' ), ); // Every 30 minutes $schedules['every_thirty_minutes'] = array( 'interval' => 30 * 60, 'display' => __( 'Every 30 Minutes' ), ); return $schedules; } add_filter( 'cron_schedules', 'toptut_add_cron_intervals' );
Once registered, the custom schedule name (every_five_minutes) can be used in any wp_schedule_event() call as the recurrence parameter.
WP-CLI Cron Commands: Complete Reference
| Command | What It Does |
|---|---|
| wp cron event list | Lists all scheduled cron events with next run time and recurrence |
| wp cron event run {hook} | Manually runs a specific cron event immediately β useful for testing |
| wp cron event delete {hook} | Removes a scheduled event (next occurrence only) |
| wp cron event schedule {hook} {time} {recurrence} | Schedules a new cron event from the command line |
| wp cron schedule list | Lists all registered cron schedule intervals (hourly, daily, custom) |
| wp cron test | Tests whether WP-Cron is working on your site β returns a pass/fail with details |
| wp option get cron | Outputs the raw cron option from the database β the actual data structure WordPress uses |
| wp cron event list –due-now | Shows only events that are currently due to run β useful for debugging missed events |
wp cron test after setting up your server cron job. It will return a pass/fail and show the last time cron ran. If it returns a failure even after your server cron is set up, the issue is usually the path to wp-cron.php in your crontab command β double-check it matches the actual file system path of your WordPress installation.
See All Your WordPress Cron Jobs at a Glance
The TopTut Cron Manager plugin shows every scheduled event, flags overdue and stuck tasks, and walks you through replacing WP-Cron with a real server cron job β without touching the command line.
Get the Free Plugin βFrequently Asked Questions
DISABLE_WP_CRON is set to true in wp-config.php without a real cron job replacing it, the server is blocking loopback HTTP requests (check Tools β Site Health), the site has too little traffic to trigger cron reliably, or a caching plugin is intercepting requests to wp-cron.php. Work through the five-step diagnostic in this guide, starting with checking wp-config.php for the disable flag.define('DISABLE_WP_CRON', true); to your wp-config.php file, above the line that says /* That's all, stop editing! */. This prevents WordPress from spawning HTTP requests to wp-cron.php on every page load. Important: once you disable WP-Cron, your scheduled tasks will stop running until you set up a real server-side cron job to replace it. Set up the server cron job before or immediately after disabling WP-Cron.wp cron event list. Both show every registered event, its next run time, and its recurrence interval. WP-CLI additionally lets you run events manually with wp cron event run {hook}.