Magento 2 Experten — Hyvä Theme, Tailwind CSS & SEO aus einer Hand ›

Defining a Custom Cron Group: crontab.xml

Defining a Custom Cron Group: crontab.xml

~7 Min. Lesezeit Zuletzt aktualisiert am August 9, 2026

Chapter 33 needs a daily cron job that expires points. Before that job exists, this chapter settles a design question: does it run in the standard default crongroup, alongside indexing, email delivery, and dozens of core jobs - or in its own, isolated group? Per the specification: its own group, ID mironsoft_loyalty.

Why a dedicated group?

  • Isolation: a slow or stuck job in default can, depending on configuration, delay processing of other default jobs. A dedicated group keeps the loyalty program separate from the rest.
  • Its own schedule history: history_success_lifetime/history_failure_lifetime can be configured per group - a daily job needs different retention than a per-minute one.
  • An optional separate process: the optional <use_separate_process> flag lets cron:run execute this group in its own subprocess.

etc/crontab.xml

app/code/Mironsoft/Loyalty/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="mironsoft_loyalty">
        <job name="mironsoft_loyalty_expire_points"
             instance="Mironsoft\Loyalty\Cron\ExpirePoints"
             method="execute">
            <config_path>mironsoft_loyalty/cron/expire_points_schedule</config_path>
        </job>
    </group>
</config>

<config_path> instead of a hardcoded <schedule> cron expression: Magento then reads the cron expression from a configuration value - exactly the "own cron_run_frequency configuration" the specification calls for. A shop operator can later change the run time under Stores > Configuration without needing a new module release.

The matching configuration value

Chapter 7 established the four paths under mironsoft_loyalty/general/*. This chapter adds a new cron group in system.xml with a fifth configuration path, mironsoft_loyalty/cron/expire_points_schedule.

<!-- Addition in app/code/Mironsoft/Loyalty/etc/adminhtml/system.xml,
     inside the existing <section id="mironsoft_loyalty"> from chapter 7 -->
<group id="cron" translate="label" sortOrder="20"
       showInDefault="1" showInWebsite="0" showInStore="0">
    <label>Cron Settings</label>
    <field id="expire_points_schedule" translate="label,comment" type="text" sortOrder="10"
           showInDefault="1" showInWebsite="0" showInStore="0">
        <label>Expire Points Cron Expression</label>
        <comment>Standard cron syntax, e.g. "0 2 * * *" for daily at 2am.</comment>
    </field>
</group>
<!-- Addition in app/code/Mironsoft/Loyalty/etc/config.xml,
     inside <default><mironsoft_loyalty> from chapter 7 -->
<cron>
    <expire_points_schedule>0 2 * * *</expire_points_schedule>
</cron>

Group-specific runtime settings

How far ahead the schedule generator plans, how long a scheduled run stays valid, and how long history is kept is configured by Magento per group under system/cron/<group_id>/* - added in config.xml, not in crontab.xml.

<!-- Further addition in app/code/Mironsoft/Loyalty/etc/config.xml -->
<system>
    <cron>
        <mironsoft_loyalty>
            <schedule_generate_every>60</schedule_generate_every>
            <schedule_ahead_for>120</schedule_ahead_for>
            <schedule_lifetime>180</schedule_lifetime>
            <history_cleanup_every>60</history_cleanup_every>
            <history_success_lifetime>4320</history_success_lifetime>
            <history_failure_lifetime>10080</history_failure_lifetime>
        </mironsoft_loyalty>
    </cron>
</system>

schedule_generate_every is deliberately set to 60 (minutes) instead of the core-typical 1: a single daily job doesn't need minute-level schedule generation. history_success_lifetime, at three days (4320 minutes), is shorter than many core groups because the real history already lives permanently in the points ledger (chapter 3) - the cron_schedule table only needs to serve short-term troubleshooting here.

Achtung: The cron expression, whether in <schedule> or the config_path value, is evaluated against the configured timezone (general/locale/timezone, default scope) - not necessarily the server's system timezone. 0 2 * * * means "2am in the shop's configured timezone", which causes confusion especially on UTC servers with a shop configured for a different timezone ("why does the job only run at 4am server time?"). Changing general/locale/timezone shifts the actual execution time accordingly - without the cron expression itself ever being touched.

Don't forget the master cron

A new crongroup alone isn't enough - Magento's own "cron over cron" mechanism (cron:run, setup:cron:run, update:cron:run) must run regularly for anything to be scheduled and executed at all. In this project's Mark Shust setup, a dedicated cron container already handles that - a quick look confirms it's running.

# Run every due job across every group
bin/magento cron:run

# Trigger only this group manually (e.g. for local testing)
bin/magento cron:run --group=mironsoft_loyalty

Tipp: --group=mironsoft_loyalty is only needed for manual testing or a deliberately separate crontab line on a different schedule - the Mark Shust cron container's standard cron:run already covers every group, including this new one.

With the group and schedule in place, chapter 33 writes the actual job class: ExpirePoints.