Monitor WordPress cron health: a plugin that warns you when scheduled tasks stop running
· 5 min read
WordPress scheduled events fail without making a sound. A backup plugin schedules a daily job, the job stops running, and you hear nothing because the notification email is generated by the job itself. Eleven days later you ask for a restore and discover the last backup was almost two weeks ago.
This is not a bug. It is how the trigger mechanism works. WordPress does not run its cron queue on a timer. It runs it when a visitor loads a page and the scheduled time has passed. A low-traffic site on a quiet night may go hours without a visitor. A site where DISABLE_WP_CRON is set to true with no server cron to replace it may go indefinitely.
A small monitoring plugin breaks the silence. It checks every scheduled event against its next run time, flags the ones that are overdue on the admin page and in the Site Health panel, and optionally emails the site admin when events fall behind. It does not fix anything, but it replaces silence with a visible signal.
What one build gives you
- Admin page listing every scheduled event with overdue, pending, and OK status
- DISABLE_WP_CRON detection and warning displayed before the event table
- Site Health check that surfaces overdue events as a critical issue on the health dashboard
- Daily email alert to the site admin when any scheduled event is overdue
- Automatic cleanup of the scheduled alert event on plugin deactivation
What it does not do
- Events that ran but failed mid-execution without throwing a catchable error
- Why a specific event is overdue, only that it is
- Rescheduling or restarting missed events, the plugin reports only
- Email alerts when DISABLE_WP_CRON is set and no server cron is configured, since the daily check itself relies on cron
The prompt
Loads into the composer so you can edit it first. Nothing is built, and nothing is charged, until you send it.
Build a WordPress plugin called WP Cron Monitor. Plugin Name: WP Cron Monitor. Description: Shows scheduled event status on an admin page, adds a Site Health check, and emails an alert when events are overdue. Single PHP file. No external dependencies. Register an admin page under the Tools menu using add_management_page. Page title: Cron Health. Menu title: Cron Health. Capability: manage_options. Slug: wp-cron-health. On the page, display two sections: 1. If the constant DISABLE_WP_CRON is defined and true, display a div with classes notice notice-warning containing a paragraph: 'DISABLE_WP_CRON is active. WordPress will not process scheduled events on page load. A server-level cron job must call wp-cron.php on a schedule, or no events will run.' 2. A table of all scheduled events. Read the cron queue by calling _get_cron_array(). For each Unix timestamp key and each hook at that timestamp, extract the schedule name and interval from the nested array (each entry has schedule and interval keys; single-run events have no schedule and interval 0, show 'once' and '-'). Calculate status: if the timestamp is more than the interval seconds in the past (and interval is greater than 0), the event is overdue; if the timestamp is within 900 seconds in the future, it is pending; otherwise it is OK. Display columns: Hook Name, Schedule, Interval, Next Run (formatted with date('Y-m-d H:i:s', $timestamp)), Status. In the Status column, output 'Overdue' with inline style color:#cc0000 if overdue, 'Pending' with color:#b06000 if pending, and 'OK' with color:#006600 otherwise. Sort rows so overdue appear first, then pending, then OK. Apply classes wp-list-table widefat fixed striped to the table. Register a Site Health check by adding a filter on the site_status_tests hook. Add an async test with id 'wpcm_overdue'. The callback reads _get_cron_array() and checks whether any events with a positive interval are overdue using the same logic above. If any are found, return a result array with status 'critical', label 'Scheduled events are overdue', and a description that lists the overdue hook names. If none are found, return status 'good' and label 'All scheduled events are on track'. On plugin activation using register_activation_hook, schedule a daily event using wp_schedule_event(time(), 'daily', 'wpcm_daily_check'). On plugin deactivation using register_deactivation_hook, call wp_clear_scheduled_hook('wpcm_daily_check'). Add an action for 'wpcm_daily_check'. The callback reads _get_cron_array(), collects all overdue events with positive intervals, and if any are found, sends an email to get_option('admin_email') with subject 'WordPress cron health alert: overdue scheduled events' and a plain-text body listing each overdue hook name and how many seconds overdue it is.
Why cron failures disappear
WordPress cron is a pull system, not a push one. The queue is checked when a visitor loads a page. If no visitors arrive during a scheduled window, the event is not triggered. A low-traffic site can drift for hours; a site with DISABLE_WP_CRON set and no server cron configured can drift forever.
The silent failure problem is compounded by how most backup and monitoring plugins report results: they report when a job runs. If the job never runs, there is no report. The UpdraftPlus status page will show the daily backup as scheduled and active whether or not it has ever fired. Nothing in the WordPress admin flags the discrepancy.
DISABLE_WP_CRON is meant to be paired with a real server cron job calling wp-cron.php on a schedule. If that server cron is missing, misconfigured, or removed after a hosting migration, WordPress never processes its queue. The admin panel looks exactly the same either way.
What the plugin checks
The plugin adds an admin page at Tools > Cron Health. It reads the full cron schedule and compares each event's next scheduled run time against the current time. Any event where the scheduled time is more than one full interval in the past has missed at least one run and is flagged as overdue.
The table shows every scheduled event: its hook name, the schedule name (daily, hourly, twicedaily, or a custom interval), the interval in seconds, the next scheduled run time, and a status column. Overdue events appear in red at the top of the list. Events due within fifteen minutes appear in orange. Everything on schedule appears in green.
If DISABLE_WP_CRON is defined as true, a warning notice appears at the top of the page before the table. This surfaces the configuration immediately, before you spend time diagnosing why events are not running.
- Tools > Cron Health admin page with live event status table
- Overdue flag for any event whose scheduled time is more than one interval in the past
- DISABLE_WP_CRON detection with a warning notice
- Site Health integration: overdue events appear as a critical issue in the health dashboard
- Optional daily email alert to the admin address when overdue events are found
The Site Health check
The plugin registers a test in the WordPress Site Health panel. When WordPress runs its background health checks, it evaluates the cron queue using the same overdue logic. If any events have missed a run, the test returns a critical result and lists the affected hook names. If everything is on schedule, it returns green.
This matters because the admin page requires someone to visit it deliberately. The Site Health check surfaces results on the dashboard without any direct action. It is the more reliable of the two signals for sites where nobody checks Tools > Cron Health on a regular basis.
What the plugin cannot detect
The plugin cannot tell you why an event is overdue, only that it is. The cause might be no visitors triggering wp-cron, DISABLE_WP_CRON with no server cron configured, a plugin that unscheduled an event without rescheduling it, or a job that ran but threw a fatal error and failed to re-register itself.
It cannot detect events that ran and failed silently. If a backup job ran, connected to storage, encountered an error, and exited without reporting, the scheduled timestamp advances normally and the plugin sees a healthy status. It sees the schedule, not the execution.
The daily email alert itself uses wp-cron. If the cron queue is completely stalled, the alert email will also not send. The on-demand admin page and the Site Health panel will still show the problem because those run on page load, not via a scheduled event. In a total cron outage, the passive signals work and the active one does not.
Questions
- All my events show overdue. What does that mean?
Usually it means the cron queue is not being processed at all. Check whether DISABLE_WP_CRON is set to true in wp-config.php and, if so, whether a server cron job is calling wp-cron.php on a schedule. On a very low-traffic site, events can also fall behind because no visitor has triggered the queue recently.
- The cron health alert email is also not arriving. Is the plugin broken?
The daily alert email is sent via a scheduled event, so it relies on wp-cron to fire. If the entire queue is stalled, the alert will not send. The admin page at Tools > Cron Health and the Site Health panel will still show the problem because both run on page load, not via a scheduled event.
- Can this plugin reschedule missed events?
No. The plugin reads the cron queue and reports on it but makes no changes to it. If an event needs to be rescheduled, the plugin that registered it handles that, or you can trigger wp-cron.php manually by visiting yoursite.com/wp-cron.php in a browser or via a curl command.
- I see an overdue event but the plugin that registered it seems to be working fine. Why?
The plugin can only see whether the next scheduled run time is in the past. It cannot confirm whether the event actually ran. If a job ran but did not re-register its next run correctly, it will appear overdue even if the last execution completed without errors.
- Is it safe to run the plugin on a live site?
Yes. The admin page and Site Health checks are read-only. They inspect the cron queue but do not modify it. The only write action is scheduling the daily alert event on activation, and deactivating the plugin removes that event cleanly.
The prompt
Loads into the composer so you can edit it first. Nothing is built, and nothing is charged, until you send it.
Build a WordPress plugin called WP Cron Monitor. Plugin Name: WP Cron Monitor. Description: Shows scheduled event status on an admin page, adds a Site Health check, and emails an alert when events are overdue. Single PHP file. No external dependencies. Register an admin page under the Tools menu using add_management_page. Page title: Cron Health. Menu title: Cron Health. Capability: manage_options. Slug: wp-cron-health. On the page, display two sections: 1. If the constant DISABLE_WP_CRON is defined and true, display a div with classes notice notice-warning containing a paragraph: 'DISABLE_WP_CRON is active. WordPress will not process scheduled events on page load. A server-level cron job must call wp-cron.php on a schedule, or no events will run.' 2. A table of all scheduled events. Read the cron queue by calling _get_cron_array(). For each Unix timestamp key and each hook at that timestamp, extract the schedule name and interval from the nested array (each entry has schedule and interval keys; single-run events have no schedule and interval 0, show 'once' and '-'). Calculate status: if the timestamp is more than the interval seconds in the past (and interval is greater than 0), the event is overdue; if the timestamp is within 900 seconds in the future, it is pending; otherwise it is OK. Display columns: Hook Name, Schedule, Interval, Next Run (formatted with date('Y-m-d H:i:s', $timestamp)), Status. In the Status column, output 'Overdue' with inline style color:#cc0000 if overdue, 'Pending' with color:#b06000 if pending, and 'OK' with color:#006600 otherwise. Sort rows so overdue appear first, then pending, then OK. Apply classes wp-list-table widefat fixed striped to the table. Register a Site Health check by adding a filter on the site_status_tests hook. Add an async test with id 'wpcm_overdue'. The callback reads _get_cron_array() and checks whether any events with a positive interval are overdue using the same logic above. If any are found, return a result array with status 'critical', label 'Scheduled events are overdue', and a description that lists the overdue hook names. If none are found, return status 'good' and label 'All scheduled events are on track'. On plugin activation using register_activation_hook, schedule a daily event using wp_schedule_event(time(), 'daily', 'wpcm_daily_check'). On plugin deactivation using register_deactivation_hook, call wp_clear_scheduled_hook('wpcm_daily_check'). Add an action for 'wpcm_daily_check'. The callback reads _get_cron_array(), collects all overdue events with positive intervals, and if any are found, sends an email to get_option('admin_email') with subject 'WordPress cron health alert: overdue scheduled events' and a plain-text body listing each overdue hook name and how many seconds overdue it is.
Steem