Cron Monitoring and Alerting in Magento 2: Watching cron_schedule Instead of Hoping
AI generated
M2
di.xml
Magento 2 · Cron · Monitoring · Alerting
Cron Monitoring and Alerting in Magento 2
watching cron_schedule instead of hoping for silent failures to stay quiet

A cron job that has not run for three days usually does not show up in the frontend right away, yet it causes silent damage exactly during that time. Systematic cron monitoring and alerting makes missed and failed jobs visible before customers or revenue suffer.

18 min read cron_schedule · health check · alerting Magento 2.4.x

1. Why cron failures stay unnoticed for so long

A missed cron job rarely shows up in the frontend right away. Order confirmation emails keep going out, the website keeps loading, only a single background process, for example a data export or a price synchronization, simply stops running. This very inconspicuousness is exactly why cron monitoring and alerting matters so much: without active watching, a silently failed job often goes undetected for days, until someone happens to notice an outdated number.

The causes of missed cron jobs are varied: a disabled system cron after a server move, a PHP error that aborts the entire process, a blocking lock left behind by a hanging predecessor job, or simply a deployment that accidentally removed the cron entry. Without cron monitoring and alerting, none of these cases look any different from a normally running system, as long as nobody actively checks.

Good cron monitoring and alerting therefore means taking the availability of cron jobs just as seriously as the availability of the website itself, applying the same principles: active checking instead of passive waiting, clear thresholds instead of gut feeling, and automatic notification instead of manual inspection.

2. cron_schedule as the data source for monitoring

The central data source for every cron monitoring effort is the cron_schedule table. Every scheduled execution of a job creates a row with job code, scheduled time, actual execution time, status, and, in case of failure, a message. Anyone who evaluates this table regularly gets a reliable overview of the actual state of all cron jobs without any additional infrastructure.


-- Jobs that failed in the last 24 hours, grouped by job code
SELECT job_code, COUNT(*) AS failures, MAX(scheduled_at) AS last_failure
FROM cron_schedule
WHERE status = 'error'
  AND scheduled_at > NOW() - INTERVAL 1 DAY
GROUP BY job_code
ORDER BY failures DESC;

-- Jobs that are stuck in "running" for longer than 30 minutes
SELECT job_code, scheduled_at, executed_at
FROM cron_schedule
WHERE status = 'running'
  AND executed_at < NOW() - INTERVAL 30 MINUTE;

-- Detect a job code that has not run successfully in the last 6 hours
SELECT job_code, MAX(finished_at) AS last_success
FROM cron_schedule
WHERE status = 'success'
GROUP BY job_code
HAVING last_success < NOW() - INTERVAL 6 HOUR;

These three queries already cover the most common cases for cron monitoring and alerting: repeatedly failed jobs, hanging jobs stuck in status running that no longer progress, and jobs that have not run successfully for an unexpectedly long time. A health check script that runs these queries periodically forms the foundation for everything else.

3. Interpreting status values correctly

The status column in cron_schedule knows the values pending, running, success, error and missed. For accurate cron monitoring and alerting, missed is especially interesting: this status is set when a scheduled job was not executed for so long that its time window (schedule_lifetime) already expired. Many missed entries within a short period are a strong signal that the system cron itself is no longer running or that a cron group is fully blocked.

A common interpretation mistake is watching only error and ignoring missed. A job that simply never started does not produce an error in the classic sense, but silently disappears in status missed without ever executing any PHP code. Complete cron monitoring and alerting must therefore watch both status values equally, not just explicit error messages.

4. A custom health check indexer for cron status

For structured cron monitoring and alerting, a dedicated health check cron job that itself regularly verifies whether other critical jobs are keeping to their expected schedule is worthwhile. This health check ideally runs in its own, isolated cron group so a failure of the main system does not immediately take it down too.


<?php
declare(strict_types=1);

namespace Vendor\Monitoring\Cron;

use Magento\Framework\App\ResourceConnection;
use Psr\Log\LoggerInterface;

/**
 * Watchdog cron job that inspects cron_schedule for jobs missing their
 * expected execution window and reports critical failures via logger.
 */
class CronHealthCheck
{
    /** @var array<string,int> Job code to maximum allowed silence in minutes. */
    private const WATCHED_JOBS = [
        'vendor_sync_products' => 45,
        'vendor_order_export' => 20,
        'catalog_product_price' => 120,
    ];

    /**
     * @param ResourceConnection $resourceConnection Direct DB access to cron_schedule.
     * @param LoggerInterface $logger Emits alerts consumed by external monitoring.
     */
    public function __construct(
        private readonly ResourceConnection $resourceConnection,
        private readonly LoggerInterface $logger
    ) {
    }

    /**
     * Checks each watched job for staleness and logs a critical alert if breached.
     *
     * @return void
     */
    public function execute(): void
    {
        $connection = $this->resourceConnection->getConnection();
        $table = $this->resourceConnection->getTableName('cron_schedule');

        foreach (self::WATCHED_JOBS as $jobCode => $maxSilenceMinutes) {
            $select = $connection->select()
                ->from($table, ['finished_at'])
                ->where('job_code = ?', $jobCode)
                ->where('status = ?', 'success')
                ->order('finished_at DESC')
                ->limit(1);

            $lastSuccess = $connection->fetchOne($select);
            $silentMinutes = $lastSuccess
                ? (time() - strtotime((string) $lastSuccess)) / 60
                : PHP_INT_MAX;

            if ($silentMinutes > $maxSilenceMinutes) {
                $this->logger->critical(sprintf(
                    'Cron job "%s" has not succeeded in %.0f minutes (limit: %d)',
                    $jobCode,
                    $silentMinutes,
                    $maxSilenceMinutes
                ));
            }
        }
    }
}

This health check is itself just another cron job, so it should run in its own group with high priority. Its purpose is purely observation, no actual business logic, which additionally makes it robust, since it brings hardly any failure sources of its own.

5. Defining sensible alert thresholds

The success of cron monitoring and alerting depends heavily on how realistically the thresholds are chosen. A job that runs every 15 minutes on schedule should trigger an alert after about 45 minutes of silence, not only after several hours. A job that runs once daily on schedule, on the other hand, needs a much more generous threshold, otherwise false alarms accumulate that desensitize the team and let real problems drown in the noise.

A proven rule of thumb for cron monitoring and alerting is to set the threshold at roughly two to three times the regular interval. A job running every 15 minutes then gets a window of 30 to 45 minutes before an alert fires, which tolerates normal delays from system load while still reliably catching real failures.

6. Connecting to external alerting systems

A log entry with level critical is not much use if nobody reads the log file. For production ready cron monitoring and alerting, detection must be connected to an active notification system, for example via a dedicated Monolog handler that forwards critical log entries to Slack, email, or a PagerDuty like system.


<?xml version="1.0"?>
<!-- app/code/Vendor/Monitoring/etc/crontab.xml -->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Cron:etc/crontab.xsd">
    <group id="vendor_monitoring_group">
        <job name="vendor_cron_health_check" instance="Vendor\Monitoring\Cron\CronHealthCheck" method="execute">
            <!-- Every 10 minutes, independent from monitored jobs -->
            <schedule>*/10 * * * *</schedule>
        </job>
    </group>
</config>

Choosing the alerting channel for cron monitoring and alerting depends on the response time actually expected. Email suits less urgent warnings, a chat channel like Slack suits daily relevant notices, and a dedicated incident system suits business critical failures that require an immediate response outside business hours.

7. Systematically analyzing cron logs

Besides the database table, Magento's standard log output also provides valuable information for cron monitoring and alerting. Errors inside a cron job that throw an exception typically end up in var/log/exception.log, while general cron activity is logged in var/log/system.log, provided the corresponding logging is enabled.

A sensible addition to plain cron_schedule analysis is a periodic scan of these log files for specific patterns, for example repeated exceptions of the same type within a short period. Cron monitoring and alerting gains a second, independent data source this way, one that also catches cases where a job is marked success but has already logged warnings internally that hint at a brewing problem.

8. Dead man switch: monitoring the watchdog itself

An often overlooked blind spot in cron monitoring and alerting is the question of who monitors the watchdog itself. If the system cron fails completely, the health check job stops running too, and in that case the monitoring system reports nothing at all, because it is no longer running itself. The solution is an external dead man switch: an outside service that expects a signal from the health check job at regular intervals and raises an alarm itself if that signal fails to arrive.

In practice, this means the health check job sends an HTTP request to an external endpoint, for example a heartbeat service, on every successful run. If this heartbeat fails to arrive within a defined window, the external service raises an alarm independently of the shop's internal state. This inversion of the monitoring direction is the only reliable way to detect a complete failure of the cron system itself, instead of relying exclusively on internal mechanisms.

9. Monitoring approaches compared

Depending on operational maturity, different combinations of cron monitoring and alerting techniques apply.

Approach Detects failed jobs Detects missed jobs Detects total cron system outage
Manually checking cron_schedule Yes, but not automatically Yes, but not automatically No
Internal health check job Yes Yes No
Log scan Yes Partially No
External dead man switch Indirectly Indirectly Yes

Complete cron monitoring and alerting combines all four layers: internal cron_schedule analysis for detailed information, a health check job for active monitoring of critical jobs, log scans for additional context, and an external dead man switch as a final safeguard against a complete failure of the cron system itself. Only this combination covers all realistic failure scenarios.

Mironsoft

Magento 2 operational reliability and monitoring

Do you find out immediately when a critical cron job fails?

We set up health check jobs, threshold alerts and external dead man switches for your Magento cron landscape, so missed or failed jobs reach you before customers notice them.

Monitoring setup

Health check jobs and realistic thresholds for your critical processes

Alerting integration

Connecting to Slack, email or incident systems based on your preferences

Dead man switch

Safeguarding against a total failure of the cron system itself

10. Summary

Effective cron monitoring and alerting in Magento 2 rests on several cooperating layers: a systematic evaluation of cron_schedule, a dedicated health check job with realistic thresholds, a connection to an active notification system, and an external dead man switch that also detects a complete failure of the cron system itself.

The most important shift in perspective is treating cron jobs as just as worthy of monitoring as the website itself. Anyone who establishes cron monitoring and alerting as a fixed part of the operations concept learns about failures within minutes, rather than only once a customer reports an outdated number or a missing confirmation.

Cron monitoring and alerting in Magento 2, the essentials at a glance

Data source

Systematically evaluate cron_schedule for failed, hanging and missed jobs.

Health check

Dedicated cron job in its own group that checks critical jobs for silence and logs alerts.

Alerting

Connection to Slack, email or an incident system, thresholds at two to three times the interval.

Dead man switch

External heartbeat service detects total cron system outage that internal monitoring cannot see.

11. FAQ: Cron Monitoring and Alerting in Magento 2

1Most important status values?
error, missed and running for suspiciously long processing.
2Why not just watch error?
Jobs that never started show up as missed, not as error.
3Sensible threshold?
Two to three times the regular interval as a rule of thumb.
4What is a dead man switch?
External service detects total cron system outage via missing heartbeats.
5Own cron group for health check?
Yes, isolated from monitored jobs, otherwise monitoring fails along with them.
6Detect a hanging job?
Status running with an executed_at timestamp far older than expected.
7Channel for critical failures?
Dedicated incident system with escalation, not just email.
8Success despite internal problem?
Possible, additional log scan for error patterns uncovers it.
9How often to run health check?
5 to 10 minutes has proven effective in practice.
10Internal monitoring alone enough?
No, external dead man switch needed to detect total outage.