Chat on WhatsApp
Magento glossary

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
How it works

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.

  1. cron:install writes 3 host crontab entries

    Running bin/magento cron:install appends three lines to the host user’s crontab — one per cron group (default, index, consumers). Each line schedules bin/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.

  2. 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 that cron:run consults each minute.

  3. cron_schedule table is the run journal

    Every job invocation gets a row in the cron_schedule MySQL table, transitioning pending → running → success | missed | error. Pending rows are picked up on the next minute; missed rows mean the cron didn’t run within schedule_lifetime; error rows include the exception message. A healthy store has zero missed/error rows older than 1 hour.

  4. 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 via system.xml under Stores → Configuration → Advanced → System → Cron.

  5. 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 the consumers cron group with --max-messages + --single-thread tuned for your workload.

When to use

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 on cron_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:reindex after catalog edits, since the cron-driven indexer pass won’t happen. Many devs ship a cron:run watch 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 workers stanza in .magento.app.yaml. You don’t install anything, but you still own monitoring: New Relic + the cron_schedule dashboard remain your responsibility.

Common mistakes

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:install as part of your post-migration checklist and verify with crontab -l on the same Linux user that owns the codebase.

  • Running consumers in the same minute as default

    The consumers group 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 as default piles up overlapping PHP processes and triggers OOM kills under load. Best practice: schedule consumers on a longer interval (e.g. every 5 minutes) or run it as a supervisor-managed daemon with queue:consumers:start <name> --single-thread.

  • Not monitoring missed/error rows

    A "missed" row in cron_schedule means 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 as www-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’s sudo -u www-data crontab -e, not crontab -e as root.

FAQ

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.

Performance audit

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.