{"id":374914,"date":"2026-09-24T11:20:49","date_gmt":"2026-09-24T11:20:49","guid":{"rendered":"https:\/\/wordpress.org\/plugins\/snagbane-cron-monitor\/"},"modified":"2026-09-24T22:30:49","modified_gmt":"2026-09-24T22:30:49","slug":"snagbane-cron-monitor","status":"publish","type":"plugin","link":"https:\/\/wordpress.org\/plugins\/snagbane-cron-monitor\/","author":23568046,"comment_status":"closed","ping_status":"closed","template":"","meta":{"version":"0.3.1","stable_tag":"0.3.1","tested":"7.1.2","requires":"5.8","requires_php":"7.4","requires_plugins":null,"header_name":"Snagbane Cron Monitor","header_author":"Snagbane","header_description":"Tells you whether WP-Cron is genuinely broken or just quiet -- instead of a generic \"something's wrong\" alarm.","assets_banners_color":"f4e8d3","last_updated":"2026-09-24 22:30:49","external_support_url":"","external_repository_url":"","donate_link":"","header_plugin_uri":"https:\/\/cronmonitor.snagbane.com\/","header_author_uri":"https:\/\/snagbane.com","rating":0,"author_block_rating":0,"active_installs":0,"downloads":71,"num_ratings":0,"support_threads":0,"support_threads_resolved":0,"author_block_count":0,"sections":["description","installation","faq","changelog"],"tags":{"0.3.0":{"tag":"0.3.0","author":"snagbane","date":"2026-09-24 11:20:26","revision":3711160},"0.3.1":{"tag":"0.3.1","author":"snagbane","date":"2026-09-24 22:30:49","revision":3712121}},"upgrade_notice":{"0.3.1":"<p>The Pro add-on&#039;s &quot;Purchase&quot; buttons on the Add-Ons screen now open the plan you clicked.<\/p>","0.3.0":"<p>Fixes a fatal error that could stop WP-Cron the first time this plugin caught a missed task, and stops tasks being reported as missed when two cron requests overlap. Multisite networks larger than 100 sites are now set up in full. Updating is strongly recommended.<\/p>","0.2.0":"<p>Renamed to Snagbane Cron Monitor. History is now a full 30-day log on every install, shown in full, instead of the last five events.<\/p>","0.1.5":"<p>Adds an optional one-time review prompt after a genuinely caught missed cron. No other behaviour changes.<\/p>","0.1.0":"<p>First release.<\/p>"},"ratings":[],"assets_icons":{"icon-128x128.png":{"filename":"icon-128x128.png","revision":3711157,"resolution":"128x128","location":"assets","locale":"","width":128,"height":128},"icon-256x256.png":{"filename":"icon-256x256.png","revision":3711157,"resolution":"256x256","location":"assets","locale":"","width":256,"height":256}},"assets_banners":{"banner-1544x500.png":{"filename":"banner-1544x500.png","revision":3711157,"resolution":"1544x500","location":"assets","locale":"","width":1544,"height":500},"banner-772x250.png":{"filename":"banner-772x250.png","revision":3711157,"resolution":"772x250","location":"assets","locale":"","width":772,"height":250}},"assets_blueprints":{},"all_blocks":[],"tagged_versions":["0.3.0","0.3.1"],"block_files":[],"assets_screenshots":{"screenshot-1.png":{"filename":"screenshot-1.png","revision":3711157,"resolution":"1","location":"assets","locale":"","width":1280,"height":1060},"screenshot-2.png":{"filename":"screenshot-2.png","revision":3711157,"resolution":"2","location":"assets","locale":"","width":587,"height":210}},"screenshots":{"1":"The verdict at a glance, with the history of what ran and what was missed below it.","2":"The dashboard widget: one sentence on whether WP-Cron is doing its job."}},"plugin_section":[262246],"plugin_tags":[4567,4688,69797,25064,4568],"plugin_category":[59],"plugin_contributors":[282127],"plugin_business_model":[],"class_list":["post-374914","plugin","type-plugin","status-publish","hentry","plugin_section-dashboard-widgets","plugin_tags-cron","plugin_tags-cron-job","plugin_tags-missed-schedule","plugin_tags-scheduled-posts","plugin_tags-wp-cron","plugin_category-utilities-and-tools","plugin_contributors-snagbane","plugin_committers-snagbane"],"banners":{"banner":"https:\/\/ps.w.org\/snagbane-cron-monitor\/assets\/banner-772x250.png?rev=3711157","banner_2x":"https:\/\/ps.w.org\/snagbane-cron-monitor\/assets\/banner-1544x500.png?rev=3711157","banner_rtl":false,"banner_2x_rtl":false},"icons":{"svg":false,"icon":"https:\/\/ps.w.org\/snagbane-cron-monitor\/assets\/icon-128x128.png?rev=3711157","icon_2x":"https:\/\/ps.w.org\/snagbane-cron-monitor\/assets\/icon-256x256.png?rev=3711157","generated":false},"screenshots":[{"src":"https:\/\/ps.w.org\/snagbane-cron-monitor\/assets\/screenshot-1.png?rev=3711157","caption":"The verdict at a glance, with the history of what ran and what was missed below it."},{"src":"https:\/\/ps.w.org\/snagbane-cron-monitor\/assets\/screenshot-2.png?rev=3711157","caption":"The dashboard widget: one sentence on whether WP-Cron is doing its job."}],"raw_content":"<!--section=description-->\n<p>If your scheduled posts are not publishing, your backups stopped running, or a plugin says its task is \"queued\" and never gets further, the usual advice is the same everywhere: check whether WP-Cron is disabled, trigger it by hand, install something that pings it once and calls it a day. None of that answers the actual question, which is whether your scheduled tasks are really not running, or whether your site is simply quiet.<\/p>\n\n<p>That distinction is the whole problem. WP-Cron is not a real system cron. It only fires when somebody visits the site, so a low-traffic site naturally goes hours between ticks. That is not the same thing as a backup silently failing for a week -- but most cron checkers treat the two identically, and you get either constant false alarms on a quiet site or a real failure nobody notices.<\/p>\n\n<p>Cron Monitor watches two separate signals instead of one:<\/p>\n\n<ul>\n<li><strong>A heartbeat<\/strong> -- has WP-Cron actually run recently at all?<\/li>\n<li><strong>A per-task grace window<\/strong> -- did each individually scheduled event fire within a reasonable time of when it was due?<\/li>\n<\/ul>\n\n<p>Together those tell \"cron is fine, the site is just quiet\" apart from \"cron is genuinely stuck\", and you get one plain sentence rather than a timestamp to interpret:<\/p>\n\n<ul>\n<li><strong>Working<\/strong> -- cron ran recently and tasks are firing.<\/li>\n<li><strong>Running late<\/strong> -- a little behind. Often normal on a low-traffic site.<\/li>\n<li><strong>Not working<\/strong> -- the site had visitors, which normally trigger cron, and it still has not run.<\/li>\n<li><strong>Not enough data yet<\/strong> -- just installed, no baseline yet.<\/li>\n<li><strong>Cannot record anything<\/strong> -- the plugin could not create the table it keeps history in, usually a database user without permission to create tables. It says so rather than reporting an all-clear it has no basis for.<\/li>\n<\/ul>\n\n<p>A site deliberately running a real server crontab with <code>DISABLE_WP_CRON<\/code> set is recognised as the healthy setup it is, rather than reported as broken because WordPress is not the one spawning the request.<\/p>\n\n<h4>What you get<\/h4>\n\n<ul>\n<li>The honest verdict above, on its own admin page and as a dashboard widget.<\/li>\n<li>A 30-day history of every scheduled task this site ran or missed, fully paginated. Everything kept is shown; there is no cap on either.<\/li>\n<li>Multisite support, including sites created after the plugin was network-activated.<\/li>\n<li>No configuration. It starts watching the moment you activate it.<\/li>\n<li>Available in English, German, Spanish, French, Italian, Dutch, Polish and Brazilian Portuguese.<\/li>\n<\/ul>\n\n<h4>Free vs. Pro<\/h4>\n\n<p>Everything described above is free, on every install, with no cap, no time limit and no nag screens. There is no locked feature anywhere in this plugin.<\/p>\n\n<p><strong>Snagbane Cron Monitor Pro<\/strong> is a separate, optional add-on (not included in this download and not distributed through WordPress.org). It does things this plugin does not do at all:<\/p>\n\n<ul>\n<li><strong>Why it broke, not just that it broke.<\/strong> Runs the checks a developer would run by hand: whether this site can reach its own wp-cron.php, whether something is answering that request with a password prompt or a refusal, whether the run lock is stuck, and whether scheduled events belong to a plugin that is no longer installed. Each answer comes with the specific next step.<\/li>\n<li><strong>Which plugin is responsible<\/strong> for any given task, by name.<\/li>\n<li><strong>What your tasks cost<\/strong> -- duration and peak memory for every run, ranked by total time, with a per-day trend that catches a task getting slower as the data underneath it grows.<\/li>\n<li><strong>Tasks that start and never finish<\/strong> -- the case a \"did it run\" check reports as a success, and the reason everything scheduled behind it never gets its turn.<\/li>\n<li><strong>\"Run now\"<\/strong> for any overdue task, without waiting for the next tick.<\/li>\n<li><strong>Email and webhook alerts<\/strong> (Slack, Discord and anything else that accepts an incoming webhook), de-duplicated so a stuck task tells you once rather than every few minutes.<\/li>\n<\/ul>\n\n<p>Pro is available from cronmonitor.snagbane.com and requires this free plugin to be installed and active.<\/p>\n\n<h3>External Services<\/h3>\n\n<p>This plugin connects to one external service:<\/p>\n\n<p><strong>Freemius<\/strong>, our licensing and update platform. The Freemius SDK is used to check for updates, apply the Free\/Pro state, and -- only if you opt in on first activation -- share basic, non-personal usage and site data used to improve the plugin. This connects to freemius.com. No data is sent unless you activate the plugin, and the optional opt-in can be declined without affecting anything. See the <a href=\"https:\/\/freemius.com\/privacy\/\">Freemius Privacy Policy<\/a> and <a href=\"https:\/\/freemius.com\/terms\/\">Terms of Service<\/a>.<\/p>\n\n<p>The plugin makes no other outbound requests. Its detection runs entirely on your own server, observing your own site's existing WP-Cron activity.<\/p>\n\n<!--section=installation-->\n<ol>\n<li>Install through the WordPress plugins screen, or upload the plugin files to <code>\/wp-content\/plugins\/snagbane-cron-monitor<\/code>.<\/li>\n<li>Activate it through the \"Plugins\" screen.<\/li>\n<li>Open \"Cron Monitor\" in your admin sidebar. There is nothing to configure -- the verdict is there on the first page load, and the history fills in as cron runs.<\/li>\n<\/ol>\n\n<!--section=faq-->\n<dl>\n<dt id=\"my%20scheduled%20posts%20are%20not%20publishing.%20will%20this%20tell%20me%20why%3F\"><h3>My scheduled posts are not publishing. Will this tell me why?<\/h3><\/dt>\n<dd><p>It will tell you whether the cause is cron. A post scheduled for a time that has passed and never went live means WordPress never got to run its publishing task, and this plugin shows you whether cron has been running at all, when it last ran, and which tasks were missed. If cron is running fine and the post still did not publish, the cause is elsewhere and you have ruled out the most common culprit. Pro goes further and tells you which specific thing is blocking cron.<\/p><\/dd>\n<dt id=\"will%20this%20tell%20me%20my%20site%20is%20broken%20just%20because%20it%20is%20quiet%3F\"><h3>Will this tell me my site is broken just because it is quiet?<\/h3><\/dt>\n<dd><p>No, and that is the specific thing this plugin exists to get right. A stale heartbeat is only reported as \"Not working\" when the site actually had recent visitors and cron still did not run. Otherwise it is reported as \"Running late\", which is a much less alarming and much more accurate thing to say.<\/p><\/dd>\n<dt id=\"does%20this%20replace%20wp-cron%2C%20or%20change%20how%20my%20tasks%20run%3F\"><h3>Does this replace WP-Cron, or change how my tasks run?<\/h3><\/dt>\n<dd><p>No. It only observes. It never reschedules, disables or modifies another plugin's events. The one thing it schedules is its own five-minute heartbeat, which does nothing except give WP-Cron a predictable reason to tick.<\/p><\/dd>\n<dt id=\"i%20use%20a%20real%20server%20cron%20with%20disable_wp_cron.%20does%20that%20work%3F\"><h3>I use a real server cron with DISABLE_WP_CRON. Does that work?<\/h3><\/dt>\n<dd><p>Yes, and it is detected. With that constant set, the verdict switches to checking whether your server cron is actually reaching wp-cron.php, and says so plainly either way instead of reporting the healthiest possible setup as a fault.<\/p><\/dd>\n<dt id=\"what%20is%20in%20the%20history%20log%3F\"><h3>What is in the history log?<\/h3><\/dt>\n<dd><p>Every individually scheduled task this plugin watched, whether it ran or was missed, with how late it was. Not just the overall heartbeat. Thirty days on every install, and the screen shows all of it.<\/p><\/dd>\n<dt id=\"does%20the%20free%20version%20name%20the%20plugin%20behind%20a%20missed%20task%3F\"><h3>Does the free version name the plugin behind a missed task?<\/h3><\/dt>\n<dd><p>No. Naming the plugin or theme responsible is a Pro feature. The free log shows that a task ran or was missed, and when.<\/p><\/dd>\n<dt id=\"does%20it%20slow%20my%20site%20down%3F\"><h3>Does it slow my site down?<\/h3><\/dt>\n<dd><p>No. A front-end page view reads one option and, at most once a minute, writes it back. Everything else happens inside the WP-Cron request, which is a background request already.<\/p><\/dd>\n\n<\/dl>\n\n<!--section=changelog-->\n<h4>0.3.1<\/h4>\n\n<ul>\n<li>Fixed: on the Add-Ons screen, every plan's \"Purchase\" button in the Pro add-on's details opened checkout for the largest plan. Each button now opens its own plan.<\/li>\n<\/ul>\n\n<h4>0.3.0<\/h4>\n\n<ul>\n<li>Fixed: on a host where the table could not be created, the plugin reported \"Working\" -- a confident all-clear from a plugin that had not managed to store a single row. It now says so plainly instead.<\/li>\n<li>Fixed: a site that a network activation skipped got its table but never its scheduled events, so it recorded nothing and its log was never trimmed.<\/li>\n<li>Fixed: network deactivation stopped at 200 sites, leaving a daily event scheduled on every site past that with nothing left to run it.<\/li>\n<li>Fixed: a fresh install on a server-cron site was declared broken for up to an hour, until the first crontab tick. It now says there is not enough data yet, which is what is actually true.<\/li>\n<li>Fixed: a cron request that did no work -- because another one held the lock -- still stamped the heartbeat and reported the tasks the other request was busy running as missed. Whether a request did the work is now decided on what it was actually seen to do, with the lock only as a fallback, so a run long enough to lose its lock to a later request still reports correctly and a request that was turned away reports nothing.<\/li>\n<li>Added: the history now shows how late each task was, which was recorded all along and never displayed, and a task that failed is marked as failed rather than shown as having run.<\/li>\n<li>Fixed: a fatal error inside the cron request the first time this plugin ever caught a missed task. The review-prompt listener was registered to receive one argument and declared as needing three, which PHP 8 raises an ArgumentCountError for -- so WP-Cron died at the exact moment the plugin did its job. It took the rest of the queue down with it.<\/li>\n<li>Fixed: tasks were reported as missed when a second cron request arrived while the first was still running. WordPress allows one cron run at a time and the second request returns immediately without doing anything, but it is still a cron request -- so every task the first request was busy running looked overdue from inside it. On a busy site, where overlapping cron requests are normal, that produced failures that never happened. This now checks the run lock and only reports on a request that actually processed the queue.<\/li>\n<li>Fixed: on multisite, network activation only reached the first 100 sites, and deactivation and deletion the same. <code>get_sites()<\/code> defaults to 100 and has no \"all\" value, so every site past the hundredth silently had no tables and no scheduled events -- and kept its table forever on deletion. Activation now covers 200 sites inline and every site beyond that sets itself up on its first load; deletion pages through the whole network.<\/li>\n<li>Fixed: the schema version was recorded even when the table could not be created, for instance on a database user without CREATE. The plugin then believed it was installed and every write went to a table that was not there. The version is now recorded only once the table is confirmed.<\/li>\n<li>Fixed: on a host where the table genuinely cannot be created, the plugin retried on every single request, front end included. It now retries at most once an hour.<\/li>\n<li>Fixed: deleting the plugin left its review-prompt option and, on a site with WP-Cron disabled, both of its scheduled events behind.<\/li>\n<li>Added: the plugin is now translatable, and ships translated into German, Spanish, French, Italian, Dutch, Polish and Brazilian Portuguese. A translation template is included, and further community translations at translate.wordpress.org are welcome -- anything published there is used in preference to the copies bundled here.<\/li>\n<li>Changed: the review prompt now waits for three confirmed missed tasks rather than one. One can be the tail of a single bad afternoon on the host, and a plugin installed this morning asking for a review this afternoon is a nag however it is worded.<\/li>\n<\/ul>\n\n<h4>0.2.0<\/h4>\n\n<ul>\n<li>Changed: history is one fixed 30-day window on every install, kept and displayed in full, with pagination. Previously the log was retained for 14 days and only the last five rows were shown.<\/li>\n<li>Changed: renamed from \"Snagbane - Cron Verdict\" to \"Snagbane Cron Monitor\" (slug: snagbane-cron-monitor). All internal class, function, constant, hook, option and table names updated to match.<\/li>\n<li>Fixed: the <code>Plugin URI<\/code> header pointed at this plugin's own wordpress.org listing page, which WordPress.org's header requirements disallow. Now points to cronmonitor.snagbane.com.<\/li>\n<li>Fixed: the plugin header's <code>Description<\/code> was over WordPress.org's length limit for that field, as was this readme's short description.<\/li>\n<li>Changed: header <code>License<\/code> from <code>GPL-2.0-or-later<\/code> to <code>GPLv2 or later<\/code>, matching WordPress.org's own documented example.<\/li>\n<\/ul>\n\n<h4>0.1.5<\/h4>\n\n<ul>\n<li>Added: an optional, one-time review prompt on this plugin's own admin page after it catches a genuinely missed cron job, rather than on a timer. Dismissible, never shown again once answered either way, and not tied to any tracking or external service.<\/li>\n<\/ul>\n\n<h4>0.1.4<\/h4>\n\n<ul>\n<li>Fixed: the Text Domain header and every translation call now match the repository slug the plugin's name generates.<\/li>\n<\/ul>\n\n<h4>0.1.3<\/h4>\n\n<ul>\n<li>Changed: readme rewritten so the description uses the phrases people actually search for. No code changes.<\/li>\n<\/ul>\n\n<h4>0.1.2<\/h4>\n\n<ul>\n<li>Fixed: the Text Domain header and every translation call were corrected to match the plugin's own folder name, clearing 34 mismatch errors reported by WordPress.org's Plugin Check tool.<\/li>\n<li>Hardening: direct database queries against this plugin's own log table are individually documented with the reason no caching API applies, and the multisite uninstall loop moved into its own function so its variables are not assigned at global scope.<\/li>\n<\/ul>\n\n<h4>0.1.1<\/h4>\n\n<ul>\n<li>Fixed: a hook scheduled more than once at the exact same due timestamp with different arguments -- a pattern some batch-processing plugins use -- could have one occurrence vanish from history, logged as neither ran nor missed. Every occurrence is now tracked independently.<\/li>\n<\/ul>\n\n<h4>0.1.0<\/h4>\n\n<ul>\n<li>First release: heartbeat and per-task verdict engine, dashboard widget, history log.<\/li>\n<\/ul>","raw_excerpt":"Tells a genuinely stuck WP-Cron apart from a quiet site that simply had no visitors -- instead of crying wolf, or saying nothing at all.","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin\/374914","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin"}],"about":[{"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/types\/plugin"}],"replies":[{"embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/comments?post=374914"}],"author":[{"embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wporg\/v1\/users\/snagbane"}],"wp:attachment":[{"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/media?parent=374914"}],"wp:term":[{"taxonomy":"plugin_section","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_section?post=374914"},{"taxonomy":"plugin_tags","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_tags?post=374914"},{"taxonomy":"plugin_category","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_category?post=374914"},{"taxonomy":"plugin_contributors","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_contributors?post=374914"},{"taxonomy":"plugin_business_model","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_business_model?post=374914"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}