What is Magento Cron?
Magento cron is the system that runs Magento’s scheduled background tasks. It ships with three cron groups (default, index, consumers) running at one-minute intervals via the host crontab. Each module registers tasks in crontab.xml; the runner queues them in the cron_schedule table and executes them based on schedule. Critical for indexer maintenance, order email sending, scheduled price changes, sitemap regeneration, and 100+ other background jobs.
Misconfigured cron is the #1 silent-failure source in Magento. Adobe-Certified Magento developer explains what cron is, how the 3 groups work, when to use each, and the four mistakes that bite stores in production.
- Cron groups 3 (default · index · consumers)
- Run interval Every minute (system cron)
- Failure mode #1 silent killer in Magento
The five moving parts of Magento cron
Magento cron is five primitives in a chain: host crontab, module crontab.xml, the cron_schedule table, per-group runtime config, and the consumer process model. Once you understand all five, every cron debug is mechanical.
-
cron:install writes 3 host crontab entries
Running
bin/magento cron:installappends three lines to the host user’s crontab, one per cron group (default,index,consumers). Each line schedulesbin/magento cron:run --group=<name>at one-minute intervals. The host crontab is the actual scheduler; Magento cron is just the workload Magento publishes for it. -
crontab.xml registers tasks per module
Each Magento module declares its scheduled tasks in
etc/crontab.xml, an XML file mapping a job name to a PHP class, a method, a cron group, and a schedule expression (cron string or config-path). At boot, Magento reads every module’s crontab.xml and merges the entries into the in-memory job registry thatcron:runconsults each minute. -
cron_schedule table is the run journal
Every job invocation gets a row in the
cron_scheduleMySQL table, transitioningpending → running → success | missed | error. Pending rows are picked up on the next minute; missed rows mean the cron didn’t run withinschedule_lifetime; error rows include the exception message. A healthy store has zero missed/error rows older than 1 hour. -
Each task has max-runtime + history-lifetime
Per-group config controls how long a task may run before being killed (
max_running_time), how far ahead jobs are scheduled (schedule_ahead_for), how long completed rows stay (history_success_lifetime/history_failure_lifetime), and how often the table is pruned. Tunable viasystem.xmlunder Stores → Configuration → Advanced → System → Cron. -
Consumer jobs run via queue:consumers:start
Long-running message-queue consumers (async order place, bulk product import, async-indexing, custom RabbitMQ workloads) don’t fit the once-per-minute cron loop. They run as separate processes via
bin/magento queue:consumers:start <consumer>, kept alive by a supervisor (systemd, supervisord, Docker) or invoked from theconsumerscron group with--max-messages+--single-threadtuned for your workload.
Three environments, three cron strategies
Cron isn’t optional in production, but the right setup differs by environment. Here’s the matrix.
-
Production: always on, always monitored
Every production Magento store needs cron running. Without it: order confirmation emails don’t send, indexers go stale, scheduled price changes don’t fire, currency rates freeze, sitemaps don’t regenerate, customer notifications stall. Set up the host crontab via
cron:install, alert oncron_schedule.status = "error"or"missed"rows older than 5 minutes, and pipe the cron stdout/stderr into your log aggregator. -
Local dev: optional, but reindex manually
On a local dev box you can leave cron disabled to keep CPU free, but then you must remember to run
bin/magento indexer:reindexafter catalog edits, since the cron-driven indexer pass won’t happen. Many devs ship acron:runwatch loop in their dev container so production parity is maintained without polluting the host crontab. -
Adobe Commerce Cloud: pre-configured
On Adobe Commerce Cloud, cron is pre-configured by the platform, the three groups run automatically at one-minute intervals on the worker container, and consumers run via the platform’s
workersstanza in.magento.app.yaml. You don’t install anything, but you still own monitoring: New Relic + thecron_scheduledashboard remain your responsibility.
Four cron mistakes that silently kill production
Every cron debug ticket I’ve handled in the last 3 years traces back to one of these four. Audit your setup against them now, cheaper than the post-mortem.
-
Forgetting cron:install after a server migration
The single most common silent-killer in Magento ops. The codebase deploys cleanly to the new server, the storefront loads, the admin loads, but no host crontab entries exist, so no Magento jobs run. Symptoms appear hours later: indexers stale, orders not emailing, sitemap missing. Always re-run
bin/magento cron:installas part of your post-migration checklist and verify withcrontab -lon the same Linux user that owns the codebase. -
Running consumers in the same minute as default
The
consumersgroup spawns long-running workers that can hold large amounts of memory (async-indexing on a 500k-SKU catalog, bulk import workers). Running it on the same one-minute cadence asdefaultpiles up overlapping PHP processes and triggers OOM kills under load. Best practice: scheduleconsumerson a longer interval (e.g. every 5 minutes) or run it as a supervisor-managed daemon withqueue:consumers:start <name> --single-thread. -
Not monitoring missed/error rows
A "missed" row in
cron_schedulemeans the job was scheduled but never ran, cron itself stopped, a previous run held the lock too long, or the host CPU was saturated. An "error" row means the job ran but threw. Both fail silently from a customer’s perspective until something visible breaks (orders stop emailing, prices stop updating). Add a 5-minute alert:SELECT COUNT(*) FROM cron_schedule WHERE status IN ("missed", "error") AND scheduled_at > NOW() - INTERVAL 1 HOUR. -
Cron user differs from web user
When the host crontab runs as
root(or a different Linux user) but the web server runs aswww-data, every cron-written file (cache, generated/, var/log) ends up owned by the wrong user, the next web request can’t write to it and Magento crashes with permission errors. Always install cron under the same user that runs PHP-FPM. On most stacks that’ssudo -u www-data crontab -e, notcrontab -eas root.
Six questions devops teams ask about Magento cron
My cron isn’t running, what’s the first thing to check?
Run crontab -l on the Linux user that owns the codebase (usually www-data or your deploy user, NOT root). You should see three lines invoking bin/magento cron:run --group=default|index|consumers. If they’re missing, run bin/magento cron:install. If they’re present but jobs still aren’t running, check var/log/cron.log and var/log/magento.cron.log for the last lines, you’ll typically see a PHP fatal, a permission denied, or a database connection error. The third place to look is SELECT * FROM cron_schedule ORDER BY scheduled_at DESC LIMIT 20, if everything is "missed", cron isn’t executing at all; if it’s "error", individual jobs are throwing.
My cron_schedule table grew to millions of rows, how do I fix it?
Two-step fix. Short term: truncate the old rows. DELETE FROM cron_schedule WHERE scheduled_at < NOW() - INTERVAL 7 DAY, this is safe; the cron history is informational, not state. Long term: tune the history-lifetime configs in Stores → Configuration → Advanced → System → Cron. Defaults are history_success_lifetime = 60 minutes and history_failure_lifetime = 600 minutes, but the cleanup cron itself only runs every 10 minutes, on a high-traffic store with hundreds of jobs/minute the table can still grow large. Drop both lifetimes and run the cleanup job more aggressively, or add a nightly DB cleanup script. The root cause is almost always a misbehaving custom cron job that schedules itself far too frequently.
Should the cron user be different from the web user?
No, they should be the same Linux user. When cron runs as a different user (commonly root while web runs as www-data), every file the cron writes (cache files, generated/ artifacts, log entries, sitemap.xml) ends up owned by the wrong user. The next web request can’t modify those files and you get permission-denied errors that look unrelated to cron. Standard pattern: install the crontab under the web user with sudo -u www-data crontab -e, or whatever user owns var/ and generated/ in your stack. On Docker setups, the Magento container should run as one user end-to-end.
Magento cron vs system cron, what’s the difference?
"System cron" (or "host cron") is the Linux scheduler, the daemon that reads /var/spool/cron/<user> and fires jobs at scheduled times. "Magento cron" is the workload Magento publishes to that scheduler: a registry of jobs (defined in module crontab.xml files) plus the runner (bin/magento cron:run) that the system cron invokes once per minute. Magento doesn’t replace system cron, it depends on it. Without a working host crontab there is no Magento cron. The three lines cron:install writes are how Magento connects its job registry to the host scheduler.
Adobe Commerce Cloud cron, is it auto-managed or do I configure it?
Mostly auto-managed, but not entirely. Adobe Commerce Cloud pre-configures the three cron groups (default, index, consumers) to run on the platform’s worker container at one-minute intervals, you don’t install anything. Where you DO own configuration: the workers stanza in .magento.app.yaml for long-running queue consumers (set the right commands.start and resource limits), the cron history-lifetime tuning in admin, and monitoring, New Relic + cron_schedule dashboards are still your team’s responsibility. Adobe doesn’t alert you when jobs go to "missed" or "error".
Can you show a custom cron task example?
Two pieces. First, etc/crontab.xml in your module declares the schedule: <config><group id="default"><job name="myvendor_clean_old_carts" instance="MyVendor\Cart\Cron\CleanOldCarts" method="execute"><schedule>0 3 * * *</schedule></job></group></config>. The schedule string is standard cron syntax (here: 03:00 daily). Second, the PHP class with an execute() method does the work, inject any repository/service via constructor and write your business logic. Once the module is enabled and setup:upgrade has run, the next cron tick will pick up the job and start scheduling rows in cron_schedule. Use the default group for short jobs (< 5 minutes), index for indexer-style work, and consumers for queue-message processing.
Cron broken on a live store? Or planning a migration?
I run Magento cron audits as part of every performance engagement, host crontab, schedule_lifetime tuning, consumer supervision, monitoring on cron_schedule. Fixed-price, written report, 24-hour turnaround.