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
defaultcan, depending on configuration, delay processing of otherdefaultjobs. A dedicated group keeps the loyalty program separate from the rest. - Its own schedule history:
history_success_lifetime/history_failure_lifetimecan 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 letscron:runexecute this group in its own subprocess.
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_loyaltyTipp: --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.