Title: Cron Monitor
Author: Mac
Published: <strong>September 28, 2026</strong>
Last modified: September 28, 2026

---

Search plugins

![](https://ps.w.org/mcron-monitor/assets/banner-772x250.png?rev=3716721)

![](https://ps.w.org/mcron-monitor/assets/icon.svg?rev=3716721)

# Cron Monitor

 By [Mac](https://profiles.wordpress.org/macvej/)

[Download](https://downloads.wordpress.org/plugin/mcron-monitor.1.5.6.zip)

 * [Details](https://wordpress.org/plugins/mcron-monitor/#description)
 * [Reviews](https://wordpress.org/plugins/mcron-monitor/#reviews)
 *  [Installation](https://wordpress.org/plugins/mcron-monitor/#installation)
 * [Development](https://wordpress.org/plugins/mcron-monitor/#developers)

 [Support](https://wordpress.org/support/plugin/mcron-monitor/)

## Description

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.
 * **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.
 * **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.
 * **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.
 * Nothing is sent to anyone but you. 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`.
 * `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.

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, a `critical` count
   and an `ok`
    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).
 * `cronmon_hooks`: one row per scheduled event, with the callbacks last seen on
   it and who owns
    them.

Settings live in the `cronmon_settings` option. The stopped-running check uses one
transient and
 one small option. Successful runs are kept for 7 days and failures
for 14, capped at 50,000 rows. All of it is removed on uninstall.

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. The JSON body contains the hook name, event type, start time,
   duration and, for a failure, the truncated error message.
 * **Loopback check**: the Cron Health screen, Site Health test and REST health 
   route can ask your
    own `wp-cron.php` whether it answers. This goes to your own`
   site_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.⌉⌊Scheduled Events: every WP-Cron event with its owner, last run and recent-
run sparkline.⌉[

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.⌉⌊Run History:
each run's status, duration, memory, queries and warnings.⌉[

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

[⌊Cron Health: what to fix first, who uses the cron budget, and whether wp-cron.
php is reachable.⌉⌊Cron Health: what to fix first, who uses the cron budget, and
whether wp-cron.php is reachable.⌉[

Cron Health: what to fix first, who uses the cron budget, and whether wp-cron.php
is reachable.

[⌊Schedules: every registered interval and the events using it, plus custom schedules.⌉⌊
Schedules: every registered interval and the events using it, plus custom schedules
.⌉[

Schedules: every registered interval and the events using it, plus custom schedules.

[⌊Settings: retention, email and webhook alerts, and Action Scheduler recording.⌉⌊
Settings: retention, email and webhook alerts, and Action Scheduler recording.⌉[

Settings: retention, email and webhook alerts, and Action Scheduler recording.

## Installation

 1. Install the plugin from Plugins  Add New, or upload it to `/wp-content/plugins/
    mcron-monitor/`.
 2. Activate it.
 3. Go to **Tools  Cron Monitor**.

Duplicates and the health check work straight away. Run history fills in as your
jobs run.

## FAQ

### Where do I see my cron jobs?

Go to **Tools  Cron Monitor**. _Scheduled Events_ lists every job. _Run History_
shows what has
 actually run.

### Which plugin created this cron job?

Check the **Owner** column. “Not observed yet” means the job has not run since you
installed
 Cron Monitor. Check back after it runs.

### Is this cron job safe to delete?

Cron Monitor watches each job as it runs and tells you. “0 callbacks observed across
14 runs” is
 safe to delete. “Callbacks observed during cron” is not. If it has 
not seen the job run yet, it makes no recommendation.

### Why is my cron job running twice?

It is almost always scheduled twice. The Events screen flags duplicates and shows
exactly what it
 would remove before removing anything.

### Why is WP-Cron not running?

Open the **Cron Health** screen. It checks the usual causes and tells you what to
fix. The most
 common one is a low-traffic site: WP-Cron only runs when someone 
visits. The fix is a real server cron job, and the Health screen gives you the lines
to copy.

### Will it tell me when a cron job stops running?

Yes. It checks from normal page visits and alerts you when a job is overdue twice
in a row,
 fifteen minutes apart. A site that gets no visits at all cannot check
itself, so use the REST health route with an external monitor for that.

### Can I get alerts by email?

Yes. Go to **Settings  Alerts** and enter an email address, a Slack or Discord webhook,
or both.
 The _Send test alert_ button checks that they work.

### Does it slow my site down?

No. On normal page views it does almost nothing. It only does real work while cron
jobs run.

### Does it work with other cron plugins?

Yes. It only watches and reports. It does not take over WordPress cron.

### Can it run PHP code on a schedule?

No, and it never will. Running stored code is a security risk.

### Does it work with WooCommerce?

Yes. If Action Scheduler is present, failed tasks are logged. WooCommerce is not
required.

### Does it support multisite?

Yes. Each site has its own history and settings. There is no network-wide dashboard.

## Reviews

There are no reviews for this plugin.

## Contributors & Developers

“Cron Monitor” is open source software. The following people have contributed to
this plugin.

Contributors

 *   [ Mac ](https://profiles.wordpress.org/macvej/)

[Translate “Cron Monitor” into your language.](https://translate.wordpress.org/projects/wp-plugins/mcron-monitor)

### Interested in development?

[Browse the code](https://plugins.trac.wordpress.org/browser/mcron-monitor/), check
out the [SVN repository](https://plugins.svn.wordpress.org/mcron-monitor/), or subscribe
to the [development log](https://plugins.trac.wordpress.org/log/mcron-monitor/) 
by [RSS](https://plugins.trac.wordpress.org/log/mcron-monitor/?limit=100&mode=stop_on_copy&format=rss).

## Changelog

#### 1.5.6

 * Added: screenshots of every screen on the plugin page.
 * Added: an up-to-date translation template (`languages/mcron-monitor.pot`).

#### 1.5.5

 * Security: event references from the screens’ links and forms are strictly validated
   the moment
    they are read, and an edit of an event that no longer exists is refused
   before anything is stored.
 * Security: event references in forms are printed with WordPress’s own escaping
   functions.

#### 1.5.4

 * Fixed: events whose hook name contains unusual characters (angle brackets, percent-
   encoding,
    doubled spaces) can now be run, edited, paused, pinned and deleted
   from the screens; bulk delete reports any it could not match.
 * Fixed: the event edit form’s security token is tied to the specific event being
   edited.
 * Fixed: a callback that raises huge numbers of PHP warnings no longer makes the
   run recorder use
    unbounded memory.
 * Fixed: Run History and Cron Health links keep hook names containing &, + and #
   intact.
 * Fixed: the Run History date filter rejects impossible dates.
 * Fixed: the hourly pruner is rescheduled if an earlier attempt to schedule it 
   failed.
 * Fixed: on a new install, two requests recording the schedule change log at once
   can no longer
    overwrite each other’s first snapshot.

#### 1.5.3

 * Changed: every database query is now fully prepared, and form and argument input
   is sanitized
    field by field, as required by the wordpress.org review.
 * Fixed: WordPress 6.5 no longer hits an undefined function when the database upgrade
   runs.
 * Fixed: activating or upgrading the plugin no longer rewrites every column of 
   its tables.
 * Fixed: an alert for a fatal cron run is sent in the same request, not on a later
   page load.
 * Fixed: a failed table install is no longer recorded as a finished upgrade; it
   is retried.
 * Changed: one-off events are no longer offered for duplicate removal.
 * Fixed: pinning a duplicate occurrence moves the one selected, not its earliest
   sibling.
 * Changed: duplicate removal goes through wp_unschedule_event() in batches of at
   most 100, so
    unschedule filters and concurrent cron changes are respected.
 * Fixed: alerts queued by concurrent requests are no longer lost or delivered twice.
 * Fixed: when a cron callback runs a different cron hook, what happens after it
   returns is
    recorded against the right run.
 * Fixed: {} is refused as event arguments; they must be a JSON list.
 * Fixed: two requests updating the new and disappeared events list at once no longer
   erase each
    other’s changes.
 * Fixed: hooks whose names share the first 150 characters are no longer merged 
   into one.
 * Fixed: turning recording on or off no longer undoes a settings save made at the
   same moment.

#### 1.5.2

 * Changed: the error recorder no longer reads PHP’s error-reporting level, as required
   by the
    wordpress.org review. Errors silenced with @ inside a cron callback are
   now counted against the run. Notices and deprecations are recorded only when 
   WP_DEBUG is on, matching what WordPress itself reports. PHP’s own error handling
   is unchanged.
 * Fixed: “Run now” and pinning a time no longer lose the event if WordPress refuses
   the new
    schedule. The original occurrence is put back, with a separate error
   if even that fails, and a pin is only saved once the event has actually moved.
 * Changed: webhook alerts are now sent with wp_safe_remote_post(), so a redirect
   to a local or
    private-network address is refused.
 * Fixed: the p95 duration now uses the nearest-rank percentile; it could previously
   show the
    slowest run instead.
 * Fixed: custom schedule intervals must be whole seconds. Values such as 60.9 or
   1e3 are now
    rejected instead of silently truncated.
 * Fixed: callbacks from other plugins whose names merely start with “cronmon” (
   for example
    cronmonitor_run) are no longer hidden as this plugin’s own.
 * Fixed: the Events and Cron Health screens, and the REST and WP-CLI health checks,
   no longer
    query this plugin’s tables when they are missing.
 * Fixed: network-activated sites now schedule the hourly retention pruning on every
   subsite, not
    only the main site.
 * Fixed: searching Run History on very large logs no longer stalls the database.
 * Changed: with alerts on, one fewer database query per page load.

#### 1.5.1

Maintenance release from a pre-submission audit. No feature changes and no database
change.

 * Fixed: the Scheduled Events, Run History and Schedules screens raised a fatal
   error on hosts
    built without the mbstring PHP extension. WordPress does not 
   require that extension, so the affected screens were unreachable on those hosts.
 * Fixed: a failure message is no longer carried in the address bar. It travels 
   in a short-lived,
    per-user record instead, so a crafted link can no longer put
   a stranger’s wording inside a real WordPress admin notice.
 * Fixed: a pinned recurring event whose reschedule was refused stopped recurring
   altogether.
    WordPress now completes the reschedule at its ordinary interval,
   so the event keeps running and only loses its pinned time for that one cycle.
 * Fixed: the duplicate-scheduling guard could trigger a “translation loading triggered
   too early”
    notice on WordPress 6.7 and later, which on a cron request was then
   recorded as a warning against the run being watched.
 * Documentation: the outbound requests this plugin can make are now set out under
   their own
    External services heading, and the webhook entry states exactly what
   the request body contains.

#### 1.5.0

The Alerts section of the Settings screen is now three switches, and each one hides
and disables
 everything that depends on it. Alerting behaves exactly as it did 
before for anybody who does not touch them.

 * “Send alerts” is a slider rather than a checkbox, and switching it off takes 
   the whole section
    with it — both channels, both addresses, the throttle and 
   the test button.
 * Slack and email are separate switches. Switching one off stops that channel delivering
   while
    leaving the other one alone, and the address is kept, so switching it 
   back on restores it. Before this, silencing a channel meant clearing the field
   and typing it back in later.
 * The throttle and the test button appear only once at least one channel is on:
   with neither there
    is nothing to throttle and nowhere to send a test.
 * Both channels are on after upgrading. A site that already had a webhook URL or
   an alert address
    saved keeps delivering to it with nothing to change.

#### 1.4.0

Load-time release. Nothing about what the plugin does has changed — every screen,
every alert, every
 recorded row and every REST and WP-CLI response is identical.
What changed is how much of the plugin PHP reads on a request that is not using 
it.

 * A front-end page view now loads 2 PHP files (35,307 bytes) instead of 15 (357,569
   bytes), and
    declares 1 PHP class instead of 14. The hooks are still attached
   at exactly the same moment; the class behind each one is loaded only once its
   callback has fired and its guard has passed.
 * Measured on the machine it was built on (Windows, PHP 8.4, no opcode cache), 
   the PHP that a
    front-end request no longer reads and compiles was taking **5.76
   ms and 889 KB of memory per request. It now takes 0.55 ms and 46 KB. With an 
   opcode cache in front of it the time saving is smaller — that is the point of
   an opcode cache — but the work removed is the same work.
 * Classes are loaded on first use through an autoloader instead of being required
   up front.
 * The schema version check on every page load no longer loads the storage class
   to compare one
    integer.
 * On a site with Action Scheduler (WooCommerce and others), the Action Scheduler
   recorder is no
    longer loaded on every request — only in a request that is actually
   running the queue.
 * With run recording switched off, a cron request no longer loads the recorder 
   at all.
 * tests/test-loadmap.php asserts the new load map, so a future change cannot quietly
   put it back.

#### 1.3.0

 * Run history can be switched off. The toggle sits above the status filters on 
   the Run History
    screen: with it off no bracket is attached to any cron callback,
   so nothing is measured, nothing is written, and the plugin costs a cron request
   nothing at all. Action Scheduler logging stops with it — it writes into the same
   history, and on a busy store it is the larger of the two.
 * Everything that reads run history says so while recording is off, rather than
   showing a number
    that can no longer move: the Cron Health screen explains it
   and skips the “cron has stopped” check entirely (that check reads when anything
   last ran, which a switched-off recorder freezes — it would otherwise report a
   healthy site as dead), the cron-budget breakdown says it is frozen, the retention
   projection says there is nothing arriving to project from, and the Settings screen
   names the alerts that cannot fire. The overdue alert, the Scheduled Events screen,
   Cron Health’s configuration findings and the Site Health tests all read the schedule
   instead and are unaffected.
 * The “Add a custom schedule” form moved into a panel beside the schedules table.
 * Fixed: the “Add event” button sat three pixels above the buttons next to it in
   the table
    navigation.

#### 1.2.0

 * Alert when scheduled events have stopped running. Checked from ordinary page 
   requests, because
    a dead cron runs nothing else; fires only when the same overdue
   event is seen across two checks fifteen minutes apart, so a quiet site with a
   working cron is never told otherwise.
 * Email as an alert destination, beside or instead of the webhook. One address,
   through
    wp_mail(). The test button now exercises every saved destination.
 * REST API: `GET /cronmon/v1/health` and `GET /cronmon/v1/runs`, `manage_options`,
   the same
    findings and rows as the screens and the CLI.
 * Fatal alerts now carry the callback’s error text under a new `error` key — in
   the webhook payload
    as well as in the alert email. Before this, the summary 
   overwrote it and it was lost. A webhook consumer written against 1.1.0 sees one
   added key; no existing key changed meaning.

#### 1.1.0

 * Pause a hook without deactivating its plugin. A hook paused by another cron plugin
   is recognised and
    reported as paused rather than as mysteriously interrupted
   here, and a run suppressed by ours is recorded, not silently skipped. One limit
   worth knowing: a callback that registers itself on the same hook while that hook
   is already firing can still run that one time, because WordPress has already 
   taken its copy of the list being walked.
 * An event editor: change a scheduled event’s arguments, time and recurrence in
   place, without losing
    its pin or its duplicate guard.
 * Custom recurring schedules, with a guard against deleting one an event still 
   uses — the warning
    names the event.
 * Export the scheduled events list to CSV.
 * A loopback probe on the Cron Health screen and in the Site Health test: asks 
   your own `wp-cron.php`
    whether it answers. Admin-initiated only, cached for 
   an hour, never runs on a cron request or under WP-CLI.
 * Diagnoses why cron spawning might not be happening at all, including Cavalcade,
   Cron Control,
    DISABLE_WP_CRON and `ALTERNATE_WP_CRON`.
 * A stall check for events that are scheduled but have not actually run.
 * Aggregate blame: cron time over the last seven days attributed to the plugin 
   or theme that owns the
    callbacks.
 * A recommended `WP_CRON_LOCK_TIMEOUT` value based on what this site’s events actually
   take.
 * WP-CLI commands: `wp cronmon runs`, `wp cronmon health` and `wp cronmon orphans`.
 * Interface pass across all six screens.

#### 1.0.0

 * First release.
 * Per-run history: duration, peak memory, query count, callback count and errors
   for every cron event.
 * Rows are written when an event starts, so fatals, out-of-memory kills and timeouts
   are still recorded.
 * Callback ownership resolved to the owning plugin, theme, mu-plugin or core file.
 * Verified orphan verdicts based on observed runs, distinguishing a real orphan
   from a cron-only registration.
 * Duplicate detection on hook, arguments and schedule together, with dry-run removal.
 * Optional per-event guard against duplicate recurring schedules.
 * Lock-starvation diagnosis, including the handover delay between cron processes.
 * Cron option health, including the autoload size finding.
 * Optional webhook alerts for fatals, killed runs, interval drift and starvation.
   Off by default.
 * Action Scheduler failure logging when Action Scheduler is present. Failures only
   by default.
 * Runs triggered by WP-CLI are detected and recorded as such, so they are not mistaken
   for a web
    request. (The `wp cronmon` commands themselves arrived in 1.1.)
 * Site Health integration using core’s own overdue thresholds.
 * Recurring events that appeared in the last week are badged as new; recurring 
   events that disappeared are listed on the Health screen.
 * Pin a daily or weekly event to a time of day in the site timezone, held through
   daylight-saving changes.
 * `do_action( 'cronmon_log', ... )` lets any cron callback attach notes to its 
   own run.

## Meta

 *  Version **1.5.6**
 *  Last updated **18 hours ago**
 *  Active installations **Fewer than 10**
 *  WordPress version ** 6.5 or higher **
 *  Tested up to **7.1.2**
 *  PHP version ** 8.1 or higher **
 * Tags
 * [cron](https://wordpress.org/plugins/tags/cron/)[cron job](https://wordpress.org/plugins/tags/cron-job/)
   [cron log](https://wordpress.org/plugins/tags/cron-log/)[scheduled events](https://wordpress.org/plugins/tags/scheduled-events/)
   [wp cron](https://wordpress.org/plugins/tags/wp-cron/)
 *  [Advanced View](https://wordpress.org/plugins/mcron-monitor/advanced/)

## Ratings

No reviews have been submitted yet.

[Your review](https://wordpress.org/support/plugin/mcron-monitor/reviews/#new-post)

[See all reviews](https://wordpress.org/support/plugin/mcron-monitor/reviews/)

## Contributors

 *   [ Mac ](https://profiles.wordpress.org/macvej/)

## Support

Got something to say? Need help?

 [View support forum](https://wordpress.org/support/plugin/mcron-monitor/)