Cron

One system crontab entry drives every Magento schedule; crontab.xml declares the jobs and cron groups tune how they run.

Syntax<job name="vendor_module_job" instance="Vendor\Module\Cron\JobClass" method="execute"><schedule>*/5 * * * *</schedule></job>

Magento schedules its own background work. The operating system runs one command every minute, bin/magento cron:run, and Magento decides which jobs are due. Reindexing, order confirmation emails, newsletters, currency rates and sitemap generation all depend on it. A site with no crontab entry looks healthy for a day, then indexes go stale and emails stop.

Declaring a job

A cron job is a public method on a class, declared in a module file named etc/crontab.xml. Every job sits in a group: a label used to run and tune jobs together. Unless you say otherwise, use default. When cron runs, Magento inserts rows into the cron_schedule table with status pending, runs them, and then records success, missed or error. That table is the first place to look when a job seems not to fire.

Tuning a group

Group settings live in etc/cron_groups.xml and in the admin under Stores > Configuration > Advanced > System > Cron. For the default group Magento ships schedule_generate_every 15, schedule_ahead_for 20, schedule_lifetime 15, and use_separate_process 0.

Making it take effect

After adding or editing crontab.xml, run bin/magento setup:upgrade if the module is new, then bin/magento cache:flush, because the merged cron configuration is cached. Check it with bin/magento cron:run --group=default and a look at cron_schedule. Cron code runs in the crontab area, not frontend.

Example

Example · xml
<!-- app/code/Vendor/Reports/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="default">
        <job name="vendor_reports_prune_history"
             instance="Vendor\Reports\Cron\PruneHistory"
             method="execute">
            <schedule>0 3 * * *</schedule>
        </job>
    </group>
</config>

When to use it

  • Send a nightly stock file to a supplier without tying up a web request.
  • Prune a custom log table every morning at 03:00.
  • Move a long-running sync into its own cron group so it cannot delay indexing.
  • Work out why order confirmation emails stopped going out on a staging server.

More examples

The class the job calls

The instance and method attributes in crontab.xml point here. Dependencies arrive through the constructor, exactly as in a controller. The cutoff date is bound with a placeholder rather than concatenated into the condition. Nothing registers the class anywhere else: the crontab.xml entry plus bin/magento cache:flush is all it takes for the next cron:run to pick it up.

Example · php
<?php
// app/code/Vendor/Reports/Cron/PruneHistory.php

declare(strict_types=1);

namespace Vendor\Reports\Cron;

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

class PruneHistory
{
    public function __construct(
        private readonly ResourceConnection $resource,
        private readonly DateTime $dateTime,
        private readonly LoggerInterface $logger
    ) {
    }

    public function execute(): void
    {
        $connection = $this->resource->getConnection();
        $table = $this->resource->getTableName('vendor_reports_history');
        $cutoff = $this->dateTime->gmtDate('Y-m-d H:i:s', strtotime('-30 days'));

        $deleted = $connection->delete($table, ['created_at < ?' => $cutoff]);

        $this->logger->info(sprintf('Pruned %d report rows', $deleted));
    }
}

The one entry the operating system needs

One line every minute, for all groups. cron:install refuses to overwrite an existing Magento block unless you pass -f. In containers and on managed hosting the entry is often created by the platform instead, so check for it before adding a second one: two parallel cron:run processes on the same install cause duplicated and missed jobs.

Example · bash
# Generate and install the crontab for the current shell user
bin/magento cron:install
# Crontab has been generated and saved

crontab -l
# #~ MAGENTO START <sha256 of the install path>
# * * * * * /usr/bin/php8.4 /var/www/magento/bin/magento cron:run >> /var/www/magento/var/log/magento.cron.log 2>&1
# #~ MAGENTO END <sha256 of the install path>

# Remove it again
bin/magento cron:remove

A slow job in its own group

The group id must match the group id used in crontab.xml. use_separate_process makes Magento fork a PHP process per job in this group, so a job that runs for ten minutes cannot hold up the rest of the schedule; a longer schedule_lifetime stops its rows being marked missed. Run bin/magento cache:flush afterwards, and you can then run or exclude this group alone with cron:run --group=vendor_supplier or --exclude-group=vendor_supplier.

Example · xml
<!-- app/code/Vendor/Supplier/etc/cron_groups.xml -->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Cron:etc/cron_groups.xsd">
    <group id="vendor_supplier">
        <schedule_generate_every>15</schedule_generate_every>
        <schedule_ahead_for>20</schedule_ahead_for>
        <schedule_lifetime>60</schedule_lifetime>
        <history_cleanup_every>10</history_cleanup_every>
        <history_success_lifetime>60</history_success_lifetime>
        <history_failure_lifetime>4320</history_failure_lifetime>
        <use_separate_process>1</use_separate_process>
    </group>
</config>

Discussion

  • Be the first to comment on this lesson.