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.