Cron Monitor

Cron Monitor

By Mac
Details
View on WordPress

Cron Monitor is a WP-Cron log, monitor and event manager for WordPress. It records every scheduled
event as it runs — not just what is scheduled — so you can see how long a cron job took, how much
memory it used, how many database queries it made, which callbacks were listening, what warnings it
raised, and whether it finished at all.

WordPress ships no cron log. The schedule is a list of hook names and timestamps, and once an event
fires, nothing is written down. That is why a cron job that runs twice, dies halfway, or silently
stops is so hard to pin on anything. Cron Monitor writes the record WordPress does not.

What Cron Monitor does

  • Cron run history for every WP-Cron event: status, duration, memory added, query count, callback count, warnings and fatal errors, with a sparkline of recent runs per hook.
  • Owner attribution: the plugin, theme, mu-plugin or core file behind every callback, resolved by reflection while the event runs.
  • Orphan verdicts with evidence: “0 callbacks observed across 14 runs, safe to delete” versus “callbacks observed during cron, do not delete”.
  • Duplicate cron job detection and removal: same hook, arguments and schedule, with a dry run before anything is deleted, plus an optional per-event guard that rejects duplicate schedules at source.
  • Cron Health screen: a ranked list of what to fix, lock starvation diagnosis, cron option size, a loopback check of your own wp-cron.php, a stall check, and a recommended WP_CRON_LOCK_TIMEOUT.
  • Which plugin is spending your cron budget: seven days of cron time attributed to the plugin that spent it.
  • Add, edit, run, delete and pause scheduled events, and pause a hook without deactivating its plugin.
  • Custom recurring schedules, with a guard that refuses to delete one an event still uses.
  • Pin a daily event to a time of day that survives daylight-saving changes.
  • Change tracking: recurring events that appeared in the last week are badged as new, and the ones that disappeared are listed.
  • Email and webhook alerts for fatal errors, killed runs, drift, starvation — and for scheduled events that have stopped running. Off by default; Slack and Discord work with no intermediary.
  • Action Scheduler failure logging on WooCommerce sites. Failures only by default.
  • Site Health tests, CSV export of runs and events, WP-CLI commands (wp cronmon runs, wp cronmon health, wp cronmon orphans) and a REST API (/cronmon/v1/health, /cronmon/v1/runs) for external monitoring.
  • A logging hook so your own cron callbacks can attach notes to their run.

Which plugin owns this cron job

Cron Monitor resolves each registered callback back to the plugin, theme, mu-plugin or core file
that declared it — by reflection, at the moment the event runs. No guessing from the hook name.

Whether a cron event is genuinely orphaned

A hook with no callbacks looks identical whether the plugin that owned it was deleted, or whether it
registers its callback only during cron. Tools that read the hook list from an admin request see
“None” either way. Cron Monitor watches the event actually run, so it can tell you the difference:

  • “0 callbacks observed across the last 14 runs — safe to delete”
  • “3 callbacks observed during cron — registered only in cron context, do not delete”

Events it has not observed yet are reported as exactly that. It never recommends deleting something
it has not watched.

Real duplicate cron job detection

wp_schedule_event() has no duplicate check of any kind — the single-event function has a ±10

minute one, the recurring one has none at all. Plugins that re-schedule on every activation quietly
stack up copies. Cron Monitor groups recurring events on hook, arguments and schedule, and
shows a dry-run list before removing anything. One-off events are excluded from automatic removal:
the same callback may be intentionally scheduled at different times.

Fatal errors, timeouts and the runs that never came back

The row for an event is written when it starts, not when it finishes, so an event killed by a fatal
error, an out-of-memory or a max_execution_time timeout still leaves a record. Those are the
failures a log written at the end would lose — and they are the ones you needed.

Why WP-Cron is running late

WordPress runs cron under a lock and re-checks that lock after every single event. One slow job going
over WP_CRON_LOCK_TIMEOUT hands the lock to the next process, and everything behind it waits.
Cron Monitor names the job that overran, by how much, and how long the handover took.

When a cron job stops running altogether

Every other alert needs the event to run: a fatal is caught inside the callback, drift is
measured when the late run finally starts. A scheduled event that has simply stopped never
starts, so it never trips anything. Cron Monitor checks the schedule from ordinary page requests
— the requests that still happen when cron does not — and alerts when an event has sat past its
due time across two checks fifteen minutes apart. One check would fire on every quiet site whose
first visitor in an hour is about to run the backlog; two means it genuinely did not run.

The limit is stated plainly: a site that receives no requests at all cannot notice anything from
the inside. For that case, point external monitoring at the REST health route below.

The `cron` option itself

It is autoloaded on every request. WordPress 6.6 added a size limit for autoloaded options — and it
can never apply to this one, for a reason explained on the Health screen. On a site with thousands of
stale single events, that option is on every page load.

What changed in your schedule

Install a plugin and it silently adds six recurring events; deactivate one and its events either
vanish or, worse, stay behind. Cron Monitor keeps track of the recurring schedule, badges events that
appeared in the last week as new, and lists the ones that disappeared — with when.

A time of day that survives the clocks changing

WP-Cron is interval-based: a daily event runs 86,400 seconds after its last run, which is the same
wall-clock time only until daylight-saving time changes. Pin an event to 03:00 and Cron Monitor
reschedules it in the site timezone instead, so it stays at 03:00 all year. Whole-day intervals only
— there is no such thing as “twice daily at 03:00”.

Pause a cron hook without deactivating its plugin

The event stays scheduled and keeps firing on time; only its callbacks are suppressed, and every
suppressed firing is still recorded as such. A hook paused by another cron plugin is recognised and
reported as paused, rather than as mysteriously interrupted.

Edit scheduled events in place

Change a scheduled event’s arguments, time or recurrence without losing its pin or its duplicate
guard, add new events, and create custom recurring schedules. Every write is validated first and
refused with a reason, never silently corrected.

A logging hook for your own cron callbacks

Any code running inside a cron callback can attach a note to the run being recorded:

do_action( 'cronmon_log', 'reindexed 412 products' );

It is an action, so it needs no function_exists() guard and no dependency — with Cron Monitor
inactive the call does nothing. Notes appear on the Run History screen with their offset from the
start of the run.

WP-CLI

wp cronmon runs --status=fatal, `wp cronmon health` (exits non-zero on a critical finding, so it

sits in a monitoring crontab) and wp cronmon orphans print the same verdicts and findings as the
screens, from the same code.

REST API

GET /wp-json/cronmon/v1/health returns the same ranked findings as the Cron Health screen, a
critical count and one `ok` boolean — the field an Uptime Kuma, Zabbix or Better Stack check

compares against. GET /wp-json/cronmon/v1/runs?status=fatal&since=-1%20hour&limit=20 returns
recorded runs with the same filters as wp cronmon runs, timestamps in ISO 8601 UTC.

Both require the manage_options capability; Application Passwords over HTTPS are the intended
way in. Fifty sites, one dashboard, no SSH.

What this plugin stores

Two custom database tables, per site, prefixed with your table prefix.

cronmon_runs — one row per cron event that ran:

  • the hook name
  • an md5 hash of the event’s arguments — never the argument values themselves
  • when it was scheduled, when it started, how long it took
  • peak memory, query count, number of callbacks
  • the outcome, and for a failure the error level, a count, and the first error message, truncated
    to 2,000 characters

cronmon_hooks — one row per scheduled event: the names of the callbacks last observed on it,
which components own them, and counters for how many times it has run.

Error messages can contain absolute filesystem paths, and — if a plugin puts them there — email
addresses or order references. That is why they are capped, and why arguments are hashed rather than
stored. The argument preview on the Scheduled Events screen is read live from the cron option, where
the values already live, and is never written anywhere. Deleting the plugin drops both tables and
every option and transient it created.

Settings, including the optional alert email address and webhook URL, live in one option,
cronmon_settings. The stopped-running check keeps one short-lived transient and one small option
holding the time of its next check. Everything is removed on uninstall.

No third-party libraries are bundled and nothing is minified: every file ships as its readable source.

Row retention is configurable. Successful runs default to seven days, failures to fourteen, with a
50,000-row cap.

External services

This plugin sends nothing to anyone but you. No phone-home, no analytics, no update checker, no
bundled third-party libraries.

Nothing outbound ever happens on a request you did not initiate. This plugin contacts no
third-party service of its own. It makes at most two outbound HTTP requests, both under your
control, and can send email through your site’s own mail path:

  • The optional alert webhook, to a URL you type in yourself — typically Slack or Discord. Off by
    default and empty by default. The request body is JSON and carries the hook name, the event type,
    the start time, the duration, and for a failure the truncated error message. Nothing else, and it
    only ever goes where you pointed it.
  • A loopback check — the Cron Health screen, the Site Health test and the REST health route can each
    ask your own wp-cron.php whether it answers. That request goes to your own site’s address,
    site_url(), and to no third party. It never runs on a cron request or under WP-CLI, it is cached
    for an hour, and on installs where something else manages cron spawning (Cavalcade, Cron Control,
    DISABLE_WP_CRON, ALTERNATE_WP_CRON) it does not run at all.
  • Alert emails go through wp_mail(), to one address you type in yourself. That is your site’s
    own mail path — this plugin makes no request of its own to send them.

Details

Plugin code:
mcron-monitor
Plugin version:
1.5.6
Author:
Outdated:
No
WP version:
6.5 or higher
PHP version:
8.1 or higher
Test up to WP version:
7.1.2
Total installations:
0
Last updated:
2026-09-28
Rating:
Times rated:
0
cron
cron-job
cron-log
scheduled-events
wp-cron