Magento 2’s cron system is responsible for dozens of background tasks – reindexing, sending emails, cleaning cache, synchronising with external systems. Writing your own cron job is straightforward, but understanding groups, scheduling, and how to debug when a job is not running saves hours. I show the complete picture.
How Magento cron works
Magento cron requires a single system cron entry that fires every minute:
* * * * * www-data /usr/bin/php /var/www/html/bin/magento cron:run 2>&1 | grep -v "Ran jobs by schedule"
This command reads the cron schedule table in the database, finds jobs that are due, and executes them. In DDEV it is set up automatically.
Defining your own cron job
<!-- etc/crontab.xml -->
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Cron:etc/crontab.xsd">
<group id="default">
<!-- Run every hour -->
<job name="vendor_module_sync_products"
instance="Vendor\Module\Cron\SyncProducts"
method="execute">
<schedule>0 * * * *</schedule>
</job>
<!-- Schedule configurable from admin panel -->
<job name="vendor_module_export_orders"
instance="Vendor\Module\Cron\ExportOrders"
method="execute">
<config_path>vendor_module/cron/export_orders_schedule</config_path>
</job>
</group>
<!-- Custom group runs separately from the default group -->
<group id="vendor_module">
<job name="vendor_module_heavy_import"
instance="Vendor\Module\Cron\HeavyImport"
method="execute">
<schedule>0 2 * * *</schedule>
</job>
</group>
</config>
Cron class implementation
<?php
declare(strict_types=1);
namespace Vendor\Module\Cron;
use Psr\Log\LoggerInterface;
class SyncProducts
{
public function __construct(
private \Vendor\Module\Model\ProductSyncService $syncService,
private LoggerInterface $logger
) {}
public function execute(): void
{
$this->logger->info('Starting product sync');
try {
$result = $this->syncService->sync();
$this->logger->info('Product sync complete', [
'synced' => $result->getSyncedCount(),
'errors' => $result->getErrorCount(),
'elapsed' => $result->getElapsedSeconds() . 's',
]);
} catch (\Exception $e) {
// Log and swallow - a cron exception should not kill the entire cron run
$this->logger->error('Product sync failed: ' . $e->getMessage(), [
'exception' => $e,
]);
}
}
}
Cron groups – isolation and configuration
<!-- etc/cron_groups.xml - configure your custom group -->
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Cron:etc/cron_groups.xsd">
<group id="vendor_module">
<schedule_generate_every>15</schedule_generate_every>
<schedule_ahead_for>20</schedule_ahead_for>
<schedule_lifetime>15</schedule_lifetime>
<history_cleanup_every>10</history_cleanup_every>
<history_success_lifetime>60</history_success_lifetime>
<history_failure_lifetime>600</history_failure_lifetime>
<use_separate_process>1</use_separate_process>
</group>
</config>
Debugging cron – when a job is not running
# Check the cron schedule table in the database bin/magento cron:run --group default bin/magento cron:run --group vendor_module # Run a specific job directly (bypasses scheduling) bin/magento cron:run --job-code vendor_module_sync_products # Check job history in the database SELECT job_code, status, scheduled_at, executed_at, finished_at, messages FROM cron_schedule WHERE job_code = 'vendor_module_sync_products' ORDER BY scheduled_at DESC LIMIT 20; # Statuses: pending, running, success, missed, error # Check if cron is generating schedules SELECT COUNT(*) FROM cron_schedule WHERE status = 'pending'; # Should be > 0; if 0 - cron process is not running # Clean stale cron entries bin/magento cron:install # reinstall system crontab
Configurable schedule from admin panel
<!-- etc/adminhtml/system.xml -->
<section id="vendor_module">
<group id="cron">
<label>Cron Settings</label>
<field id="export_orders_schedule" type="text">
<label>Order Export Schedule (cron expression)</label>
<comment>e.g. */30 * * * * (every 30 minutes)</comment>
</field>
</group>
</section>
Summary
Magento 2 cron is reliable when set up correctly. The most common issues are: missing system cron entry, jobs in “missed” state because the previous run took too long, and missing logging that makes debugging hard. Custom groups provide isolation – a failing job in your group does not affect the default group. Always log start, end and key metrics from your cron jobs.
