
WordPress runs background tasks, called cron jobs, for things like publishing scheduled posts,
sending emails and running backups. It keeps no record of them, so when one fails or stops you
never find out.
Cron Monitor keeps that record. It shows what ran, what failed and which plugin is behind each
job, and it tells you when something goes wrong.
Features
- Run history: every job that ran, how long it took and whether it worked.
- Filter and retry: narrow run history by date, failed runs or hook name, and retry a failed run in one click while its job is still scheduled.
- Catches crashes: fatal errors and timeouts are recorded too.
- Plugin owner: see which plugin or theme created each job.
- Safe cleanup: finds jobs left behind by deleted plugins and tells you which are safe to delete.
- Duplicate finder: spots jobs scheduled twice and removes the extras, with a preview first.
- Health check: a list of cron problems on your site, most important first, including jobs that got slower and jobs that start late.
- Alerts: email, Slack or Discord when a job crashes, runs late or stops running.
- Manage jobs: add, edit, run now, pause or delete any scheduled job.
- Pause a job: stop one job without turning off the plugin that owns it.
- Bulk actions: pause, resume or delete up to 200 jobs at once.
- Custom schedules: create your own intervals, like every 10 minutes.
- Fixed time of day: keep a daily job at 03:00, even when the clocks change.
- What changed: see which jobs appeared or disappeared in the last week.
- WooCommerce: logs failed Action Scheduler tasks.
- CSV export: download jobs and run history.
- Developer tools: WP-CLI commands and a REST API (see Technical details below).
Good to know
- Alerts are off until you turn them on.
- Alerts go only to the email address and webhook you set up; nothing else leaves your site. No tracking, no ads.
- Deleting the plugin removes everything it created.
Technical details
WP-CLI
wp cronmon runs: recorded runs. Filters:--hook,--status,--since,--limit. The
limit is a whole number from 1 to 1000 (default 20); anything else is an error, not rounded.wp cronmon health: the Cron Health findings. Exits non-zero when one is critical.wp cronmon orphans: every event with its safe-to-delete verdict.--verdictshows only one
verdict:active,unknown,cron-only,orphaned,pausedorexcluded. Any other value is
an error, not an empty list.
All three accept --format=json.
REST API
Both routes require manage_options. Use Application Passwords over HTTPS.
GET /wp-json/cronmon/v1/health: the Cron Health findings, acriticalcount and anok
boolean for Uptime Kuma, Zabbix, Better Stack and similar monitors.GET /wp-json/cronmon/v1/runs?status=fatal&since=-1%20hour&limit=20: recorded runs. Timestamps
are ISO 8601 UTC.
Logging from your own code
Inside a cron callback, attach a note to the current run:
do_action( 'cronmon_log', 'reindexed 412 products' );
With Cron Monitor inactive, the call does nothing.
Server cron setup
Add to wp-config.php:
define( 'DISABLE_WP_CRON', true );
Then add a system cron entry, every five minutes:
*/5 * * * * cd /path/to/wordpress && wp cron event run --due-now > /dev/null 2>&1
Or, without WP-CLI:
*/5 * * * * wget -q -O - https://example.com/wp-cron.php?doing_wp_cron > /dev/null 2>&1
Cron Monitor never writes to wp-config.php.
How it works
- A run is recorded when it starts, not when it finishes, so crashes and timeouts still leave a
record. - Cron Monitor attaches itself to every scheduled hook, so a hook with no callbacks now fires and
is recorded as “0 callbacks”. Nothing else changes. - Duplicates are matched on hook, arguments and schedule together. One-off events are never
removed automatically. - Late-running cron is traced to the job that held the cron lock too long, and a
WP_CRON_LOCK_TIMEOUT value is recommended.
Performance
- Front-end page views load two small PHP files and run no queries. With alerts on, one or two
queries are added unless you have a persistent object cache. - Each cron job adds two database writes, one when it starts and one when it finishes.
- Measured instrumentation cost is about 0.005 ms and 2.7 KB per cron job (PHP 8.4, one machine).
What this plugin stores
Two database tables per site:
cronmon_runs: one row per run. Hook name, an md5 hash of the arguments (never the values),
timing, memory, query count, outcome, and the first error message (up to 2,000 characters).
If your own code callscronmon_log, its notes are stored on the run too: up to 50 notes of 500
characters each, and 8,000 characters in all per run.cronmon_hooks: one row per scheduled event, with the callbacks last seen on it and who owns
them. Rows for events that never ran and have no guard or pinned time are pruned.
Options, in the WordPress options table:
cronmon_settings: your settings, including the alert email address and webhook URL.cronmon_recording_enabled: whether run history is switched on.cronmon_paused: the jobs you have paused.cronmon_schedules: your custom schedules.cronmon_blocked: the last 20 attempts the duplicate guard refused.cronmon_changelog: the jobs that appeared or disappeared recently.cronmon_alert_item_*: one row for each alert waiting to be delivered, at most 200, each dropped
after 24 hours.cronmon_alert_queueis the older single-row version of this queue and only
exists on sites upgraded from before 1.5.2.cronmon_overdue_next_check: when the stopped-running check may next run.cronmon_webhook_next_attempt: when the alert webhook may next be tried. After a webhook request
fails, it rests for 5 minutes.cronmon_db_version: the database version of the two tables.
Transients (short-lived cache entries):
cronmon_alerted_*: one per job that raised an alert, so the same job is not alerted again
within your throttle period.cronmon_overdue_seen: the overdue job seen by the stopped-running check.cronmon_spawn_status: the result of the wp-cron.php check, cached for an hour.cronmon_critical_findings: the count shown in the menu badge, cached for an hour.cronmon_event_form_<user id>: the values you typed into the Add event form if it was refused,
kept for 2 minutes so the form can be filled in again.cronmon_request_error_<user id>: an error message to show you once, kept for 1 minute.cronmon_install_retry: set for 5 minutes after a failed table install, so the install is not retried on every request.
By default, successful runs are kept for 7 days and failures for 14, capped at 50,000 rows. You
can change all three under Tools Cron Monitor Settings. No IP addresses,
user names or visitor data are stored. The only user information is your own user ID in the names
of the last two transients, and the alert email address you enter yourself.
Everything above is removed on uninstall. The one exception is a cronmon_alerted_* transient on a
site with a persistent object cache, which cannot be found by name and expires on its own within
your throttle period.
No third-party libraries are bundled and nothing is minified.
External services
This plugin contacts no third-party service of its own. No analytics, no update checker. It makes
at most two kinds of outbound request, both under your control:
- Alert webhook (optional, off by default): sent to the URL you enter, typically Slack or
Discord. A request is made only when alerts and the webhook are switched on and a job crashes,
runs late or stops running, or when you press Send test alert. It is sent at the end of the
page request, and if it fails it is retried later, no sooner than 5 minutes after, and dropped
after 24 hours. The JSON body contains these keys:event(fatal, drift or overdue),hook
(the job’s hook name),args_hash(an md5 hash, never the arguments),site(your site’s home
URL),url(a link to Run History in your WordPress admin),started_at,duration(null for drift and overdue alerts), and
message,textandcontent(the same one-sentence summary, which includes your site’s home
URL; Slack readstextand Discord readscontent). Depending on the event it also haserror
(the truncated error message of a crash),count(how many jobs are overdue),late_by,gap
andexpected(seconds). Your site’s home URL is therefore sent to the service you chose. Terms
and privacy policies: Slack
terms and
privacy policy; Discord
terms and privacy policy. - Loopback check: the Cron Health screen, Site Health test and REST health route can ask your
ownwp-cron.phpwhether it answers. This goes to your ownsite_url(). It never runs during
cron or WP-CLI, is cached for an hour, and is skipped where something else manages cron
(Cavalcade, Cron Control,DISABLE_WP_CRON,ALTERNATE_WP_CRON).
Alert emails are sent through wp_mail() to the address you enter, using your site’s own mail
setup.
Screenshots

Scheduled Events: every WP-Cron event with its owner, last run and recent-run sparkline.

Run History: each run's status, duration, memory, queries and warnings.

Duplicate scan: the events that would be kept, the duplicates that would be removed, and the button to remove them.

Cron Health: what to fix first, who uses the cron budget, and the state of the cron option, runner, starvation and storage.

Scheduled Events timeline: the next 24 hours of events, grouped by owner, with overdue events pinned to the left edge.