Magento 2 Cron Not Running: The Definitive Troubleshooting Guide
Your Magento store depends on cron for almost everything that happens behind the scenes. When cron stops running, things break quietly at first, then loudly. Indexes go stale, emails never send, sitemaps never regenerate, and your catalog turns into a ghost town of outdated data. Here is how to fix it.
TL;DR - Quick Checks
- Check crontab:
crontab -l -u www-dataand look for abin/magento cron:runentry - Check cron_schedule table: Look for stuck jobs with
SELECT * FROM cron_schedule WHERE status='running' ORDER BY scheduled_at DESC; - Run manually for errors:
bin/magento cron:runand watch the output - Clear stuck jobs:
UPDATE cron_schedule SET status='error' WHERE status='running' AND scheduled_at < NOW() - INTERVAL 1 HOUR;
Why Magento Cron Matters More Than You Think
If you have ever wondered why Magento needs cron at all, consider this: Magento 2 ships with over 60 scheduled tasks out of the box. And that number goes up with every third-party extension you install. Here is what depends on cron actually running:
- Indexing: Product prices, category assignments, catalog search, and stock status all get reindexed on schedule. Without cron, your storefront shows stale data.
- Transactional emails: Order confirmations, shipping notifications, and password resets sit in a queue that cron processes. No cron, no emails.
- Sitemap generation: Google XML sitemaps regenerate on a schedule. Stale sitemaps mean stale SEO.
- Cache management: Full page cache warming and cleanup tasks run via cron. Cache invalidation without cron means manual flushes forever.
- Import/export: Scheduled imports and exports (products, customers, stock) run as cron jobs. If you rely on automated feeds, cron is the engine.
- Currency rates: If you sell in multiple currencies, rate updates happen via cron.
- Consumer message queues: Async operations like bulk API imports and deferred stock updates depend on the consumers cron group.
The point is, a Magento store without working cron is a Magento store that is slowly falling apart. You might not notice for a day or two, but your customers will.
Step 1: Verify the Crontab Entry Exists
This is the most common issue I see, and it is the easiest to fix. Magento 2 needs a system-level crontab entry to trigger its internal scheduler. Without it, nothing runs.
Check the current crontab for your web server user:
# For Debian/Ubuntu (www-data)
crontab -l -u www-data
# For CentOS/RHEL (apache)
crontab -l -u apache
# For custom setups
crontab -l -u magento
You should see something like this:
* * * * * /usr/bin/php /var/www/html/bin/magento cron:run 2>&1 | grep -v "Ran jobs by schedule" >> /var/www/html/var/log/magento.cron.log
* * * * * /usr/bin/php /var/www/html/update/cron.php >> /var/www/html/var/log/update.cron.log 2>&1
* * * * * /usr/bin/php /var/www/html/bin/magento setup:cron:run >> /var/www/html/var/log/setup.cron.log 2>&1
The first line is the critical one. It runs bin/magento cron:run every minute. The second handles Magento component updates, and the third runs setup-related cron tasks.
Common Crontab Mistakes
- Wrong PHP path: If your system has multiple PHP versions,
/usr/bin/phpmight point to PHP 7.4 while Magento needs 8.1 or 8.2. Usewhich phpor specify the full path like/usr/bin/php8.2. - Wrong Magento root path: If Magento is installed at
/var/www/magentobut the crontab says/var/www/html, the command silently fails. - Missing crontab entirely: Run
bin/magento cron:installto let Magento create the entries for you. Verify withcrontab -l -u www-dataafterwards. - Crontab under the wrong user: If you installed cron as root but the web server runs as www-data, files created by cron will have root ownership. More on this in Step 6.
If the crontab is empty or missing the cron:run entry, install it:
# As the Magento filesystem owner
cd /var/www/html
bin/magento cron:install
# Or manually add it
crontab -e -u www-data
# Add: * * * * * /usr/bin/php8.2 /var/www/html/bin/magento cron:run 2>&1 | grep -v "Ran jobs by schedule" >> /var/www/html/var/log/magento.cron.log
Step 2: Check the cron_schedule Table
Magento stores every cron execution in the cron_schedule database table. This table is your best friend when diagnosing cron problems because it tells you exactly what ran, what failed, and what is stuck.
# Connect to your Magento database
mysql -u magento -p magento_db
# See recent cron activity
SELECT job_code, status, messages, scheduled_at, executed_at, finished_at
FROM cron_schedule
ORDER BY scheduled_at DESC
LIMIT 20;
# Find stuck jobs
SELECT job_code, status, scheduled_at, executed_at
FROM cron_schedule
WHERE status = 'running'
ORDER BY scheduled_at DESC;
Here is what the status values mean:
- pending: Scheduled but not yet executed. Normal.
- running: Currently executing. Should only last a few minutes for most jobs.
- success: Completed successfully. This is what you want to see.
- missed: The scheduled time passed but cron never picked it up. Usually means cron was not running during that window.
- error: The job ran but threw an exception. Check the
messagescolumn for details.
If you see dozens of rows with missed status, that confirms cron is not running at the system level. Go back to Step 1. If you see rows stuck in running for hours, move to Step 3.
Step 3: Clear Stuck Cron Jobs
This is probably the single most common Magento cron problem I encounter in production. A cron job starts, the PHP process dies mid-execution (out of memory, timeout, server restart, deployment), and the row in cron_schedule stays in running status forever. Magento sees that the job is "still running" and refuses to launch a new instance. Everything downstream stops.
Fix it by marking old running jobs as errored:
# Clear jobs stuck running for more than 1 hour
UPDATE cron_schedule
SET status = 'error',
messages = 'Manually cleared - stuck in running status'
WHERE status = 'running'
AND scheduled_at < NOW() - INTERVAL 1 HOUR;
# Verify the fix
SELECT status, COUNT(*) as count
FROM cron_schedule
GROUP BY status;
After clearing stuck jobs, wait one minute and check if new pending and success rows start appearing. If they do, you are back in business.
Pro tip: Set up a monitoring query or script that alerts you when any cron job has been in "running" status for more than 30 minutes. Catching stuck jobs early saves you from the cascade of issues they cause.
Step 4: Run Cron Manually for Error Output
The fastest way to see what is actually going wrong is to run cron by hand and read the output. When cron runs via the system crontab, errors go to a log file (if you configured one) or to /dev/null. Running it manually gives you instant feedback.
# Switch to your Magento root
cd /var/www/html
# Run as the web server user
sudo -u www-data php bin/magento cron:run
# For more verbose output
sudo -u www-data php bin/magento cron:run --group=default -vvv
# Run a specific group
sudo -u www-data php bin/magento cron:run --group=index
Common errors you will see:
- "Area code is not set": Usually a bug in a custom or third-party module that does not properly set the area code before executing.
- "Class does not exist": Run
bin/magento setup:di:compileto regenerate the dependency injection configuration. - "SQLSTATE connection refused": Your database is down or the credentials in
env.phpare wrong. - "Allowed memory size exhausted": The PHP CLI memory limit is too low. See Step 5.
- No output at all: Cron ran successfully but had no pending jobs. Check the cron_schedule table to confirm.
Step 5: PHP CLI Memory Limit
This one catches a lot of people off guard. PHP has separate configuration for the CLI (command line) and FPM (web server). Your php.ini for FPM might have memory_limit = 2G, but the CLI version often defaults to 128M or 256M. Magento cron jobs, especially indexers, can easily consume 2 to 4 GB of memory.
# Check which php.ini the CLI is using
php --ini
# Check the current CLI memory limit
php -r "echo ini_get('memory_limit');"
# Find and edit the CLI php.ini
sudo nano /etc/php/8.2/cli/php.ini
# Set: memory_limit = 4G
You can also set the memory limit directly in the crontab entry:
* * * * * /usr/bin/php8.2 -d memory_limit=4G /var/www/html/bin/magento cron:run 2>&1 | grep -v "Ran jobs by schedule" >> /var/www/html/var/log/magento.cron.log
Why 4 GB? Because Magento indexers load large datasets into memory. A catalog with 50,000 products and complex pricing rules can easily need 3+ GB during reindexing. If you are running into PHP memory exhaustion errors, start with 4 GB and adjust from there.
Also check the max_execution_time setting. CLI PHP usually defaults to 0 (unlimited), which is correct. If someone set a limit, cron jobs that take more than that many seconds will be killed:
php -r "echo ini_get('max_execution_time');"
# Should output: 0
Step 6: File Permissions
Magento cron creates and modifies files in several directories: var/cache/, var/log/, var/page_cache/, generated/, and pub/static/. If the cron process runs as a different user than the web server, you end up with files that one process can write but the other cannot read. This creates a cascading mess of permission errors.
The golden rule: cron must run as the same user as the web server.
# Check who owns the Magento files
ls -la /var/www/html/var/
ls -la /var/www/html/generated/
# Check who the web server runs as
ps aux | grep -E "(apache|nginx|php-fpm)" | head -5
# Fix ownership if needed
sudo chown -R www-data:www-data /var/www/html/var/
sudo chown -R www-data:www-data /var/www/html/generated/
sudo chown -R www-data:www-data /var/www/html/pub/static/
Check that the var/ directory is writable:
# Test write access as the web server user
sudo -u www-data touch /var/www/html/var/test_write
sudo -u www-data rm /var/www/html/var/test_write
If that fails, your permissions are broken. The recommended permission setup for Magento 2 production is:
# Directories: 2775 (setgid so new files inherit group)
find /var/www/html/var -type d -exec chmod 2775 {} \;
# Files: 664
find /var/www/html/var -type f -exec chmod 664 {} \;
Step 7: Magento Cron Groups Explained
Magento 2 does not run all cron jobs in a single pass. It organizes them into cron groups, each with its own schedule and configuration. Understanding these groups helps you diagnose which specific set of jobs is failing.
| Cron Group | What It Does | Default Schedule | Common Issues |
|---|---|---|---|
| default | Emails, sitemap, currency rates, cart cleanup, log rotation, newsletter queue | Every minute | Email queue backlog, stale sitemaps |
| index | All indexer operations (catalog, price, search, stock) | Every minute (runs on schedule mode only) | Memory exhaustion, stuck reindex, stale product data |
| consumers | Message queue consumers for async operations (bulk API, stock updates) | Every minute | RabbitMQ connection failures, consumer process zombies |
| staging | Content staging and preview (Commerce/Enterprise only) | Every minute | Scheduled updates not publishing on time |
You can run a specific group in isolation to narrow down problems:
# Run only the default group
sudo -u www-data php bin/magento cron:run --group=default
# Run only indexers
sudo -u www-data php bin/magento cron:run --group=index
# Run only message queue consumers
sudo -u www-data php bin/magento cron:run --group=consumers
If --group=default works but --group=index fails, you know the problem is specific to indexing (likely memory or a corrupted index). This narrows your debugging significantly.
You can view all registered cron jobs and their groups with:
grep -r "crontab" app/etc/crontab/ 2>/dev/null
# Or check the database
SELECT DISTINCT(group_id) FROM cron_schedule;
Step 8: Lock Files Preventing Execution
Magento uses lock files to prevent the same cron job from running multiple times simultaneously. These locks live in var/.lock/ (or in the database, depending on your lock provider configuration). If a lock gets orphaned (the process died without releasing it), the corresponding job will never run again until you clear it.
# Check for orphaned lock files
ls -la /var/www/html/var/.lock/
# Remove stale locks (be careful - only remove if no cron is actively running)
sudo -u www-data rm /var/www/html/var/.lock/*
# Check your lock provider in env.php
grep -A5 "lock" /var/www/html/app/etc/env.php
Magento supports two lock providers:
- db: Locks stored in the database (default). More reliable in multi-server setups.
- file: Lock files in
var/.lock/. Simpler but can leave orphans after crashes.
If you are using database locks, check the lock_info table:
SELECT * FROM lock_info;
# Clear stale DB locks
DELETE FROM lock_info WHERE created_at < NOW() - INTERVAL 1 HOUR;
For production multi-server environments, I strongly recommend using the database lock provider. File locks do not work correctly when multiple servers share a filesystem via NFS or EFS, because lock file operations are not atomic over network filesystems.
Is Your Server Exposed?
Misconfigured cron often accompanies other server misconfigurations. Run a free security scan to check for exposed files, weak headers, and open vulnerabilities.
Run Free Security ScanPro Tip: The Cron User Trap
I have seen this pattern cause hours of debugging on dozens of Magento installations. Here is the scenario: a sysadmin sets up cron as root (or a dedicated "magento" user), while Apache or Nginx runs as www-data. Everything works initially. Then cron creates files in var/cache/ and generated/ owned by root. The web server cannot write to those directories anymore. The storefront starts throwing errors. The admin panel breaks. Someone runs chmod -R 777 in desperation, which "fixes" it temporarily but creates a massive security hole.
The fix is simple: always run cron as the same user that owns the web server process. On Debian/Ubuntu, that is www-data. On CentOS/RHEL, that is apache or nginx.
# Wrong - cron running as root
* * * * * /usr/bin/php /var/www/html/bin/magento cron:run
# Right - cron running as the web server user
* * * * * sudo -u www-data /usr/bin/php /var/www/html/bin/magento cron:run
# Or better - install crontab under the correct user
crontab -e -u www-data
If you have already created permission conflicts, here is the nuclear option to reset everything:
# Reset all Magento file ownership
sudo chown -R www-data:www-data /var/www/html/
# Set correct directory permissions
sudo find /var/www/html -type d -exec chmod 2775 {} \;
# Set correct file permissions
sudo find /var/www/html -type f -exec chmod 664 {} \;
# Make bin/magento executable
sudo chmod u+x /var/www/html/bin/magento
Kubernetes Cron: A Different Beast
If you are running Magento on Kubernetes (and more shops are moving this direction), cron works differently. Instead of a system crontab, you use a Kubernetes CronJob resource that spins up a separate container on schedule.
apiVersion: batch/v1
kind: CronJob
metadata:
name: magento-cron
namespace: production
spec:
schedule: "* * * * *"
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 5
jobTemplate:
spec:
activeDeadlineSeconds: 3600
template:
spec:
containers:
- name: magento-cron
image: your-registry/magento:latest
command: ["php", "bin/magento", "cron:run"]
resources:
requests:
memory: "2Gi"
cpu: "500m"
limits:
memory: "4Gi"
cpu: "1"
restartPolicy: Never
Key differences with K8s cron:
- Shared storage matters: The cron container needs access to the same
var/andgenerated/directories as your web pods. Use a ReadWriteMany PVC (EFS on AWS, NFS, or similar). - concurrencyPolicy: Forbid is critical. Without it, Kubernetes will happily launch multiple cron pods simultaneously, causing lock conflicts and database contention.
- activeDeadlineSeconds prevents zombie cron pods from running forever. Set this based on your longest running job (indexers can take 30+ minutes on large catalogs).
- Resource limits: Set memory limits high enough for indexers. If the container hits its memory limit, Kubernetes OOM-kills it, leaving jobs stuck in "running" status in cron_schedule.
- Database locks are mandatory: File locks do not work reliably across pods, even with shared storage. Configure the database lock provider in
env.php.
Monitor your K8s cron jobs with:
# Check cron job status
kubectl get cronjobs -n production
kubectl get jobs -n production --sort-by=.metadata.creationTimestamp | tail -10
# Check for failed pods
kubectl get pods -n production | grep -E "cron.*Error|cron.*OOMKilled"
# Read cron logs
kubectl logs -n production job/magento-cron-xxxxx
Six Common Mistakes That Break Magento Cron
- Wrong user: Cron running as root while the web server runs as www-data. Files created by cron become unreadable by the web server. Always match the users.
- Wrong PHP path: Systems with multiple PHP versions where
/usr/bin/phppoints to an incompatible version. Always use the full versioned path like/usr/bin/php8.2. - Missing cron groups: Only configuring
cron:runwithout realizing that the index and consumers groups also need to run. The singlecron:runcommand handles all groups, but if you are using group-specific commands, make sure all groups are covered. - cron_schedule table bloat: Magento keeps every cron execution record by default. On a busy store, this table can grow to millions of rows, slowing down cron itself. Set
system/cron/default/history_cleanup_everyto 10 andhistory_success_lifetimeto 60 (minutes). - Ignoring the cron log: If your crontab does not redirect output to a log file, errors vanish into thin air. Always log output:
2>&1 >> /var/www/html/var/log/magento.cron.log. - Deploying without running cron:install: After a fresh deployment or Magento upgrade, the crontab can get wiped. Always verify crontab entries after deployment. Better yet, add
bin/magento cron:install --forceto your deploy script.
Putting It All Together: A Diagnostic Checklist
When Magento cron stops working, run through these checks in order. Most problems are caught in the first three steps:
- Verify crontab entry exists for the correct user:
crontab -l -u www-data - Check cron_schedule table for stuck or missed jobs
- Clear any stuck "running" jobs older than 1 hour
- Run
bin/magento cron:runmanually and read the output - Verify PHP CLI memory limit is at least 2 GB (4 GB recommended)
- Confirm file permissions and ownership match the web server user
- Check for orphaned lock files in
var/.lock/or thelock_infotable - Review
var/log/magento.cron.logandvar/log/system.logfor errors - If on Kubernetes, check pod status, resource limits, and shared storage mounts
Frequently Asked Questions
How do I know if Magento cron is running at all?
Run crontab -l -u www-data (or your web server user) and look for a line containing bin/magento cron:run. Then check the cron_schedule table in your Magento database: SELECT * FROM cron_schedule ORDER BY scheduled_at DESC LIMIT 10;. If you see recent rows with status "success", cron is running. If the table is empty or all rows are old, cron is not executing.
Why are my Magento cron jobs stuck in "running" status forever?
A cron job stuck in "running" status usually means the PHP process was killed mid-execution (out of memory, timeout, or server restart) before it could update its status to "success" or "error". Magento checks for a running status before launching a new instance of the same job, so one stuck row can block all future runs. Fix it by updating stale running jobs: UPDATE cron_schedule SET status='error' WHERE status='running' AND scheduled_at < NOW() - INTERVAL 1 HOUR;.
Should I run Magento cron as root or as the web server user?
Always run Magento cron as the same user that owns the web server process, typically www-data on Debian/Ubuntu or apache on CentOS/RHEL. Running cron as root creates files owned by root in var/, generated/, and pub/static/ that the web server user cannot read or write, which causes permission errors and breaks the storefront.
Keep Your Magento Store Secure
Cron misconfigurations are just one piece of the puzzle. Run a free exposure scan to check your store for leaked config files, weak SSL, missing security headers, and more.
Scan Your Store NowThe Bottom Line
Magento cron problems almost always come down to one of these: missing crontab entry, stuck jobs in the database, wrong user permissions, or insufficient PHP memory. The good news is that every one of these is fixable in under five minutes once you know where to look. The bad news is that if you ignore cron, your store silently degrades until someone notices that order emails stopped sending three days ago.
Make cron monitoring part of your operations workflow. Set up alerts on the cron_schedule table. Log your cron output. And always, always verify cron after every deployment.
Related reading: Magento PHP Memory Exhaustion Fix, Redis Memory Maxmemory Reached Fix. Related tools: Cron Parser, Crontab Generator, Exposure Checker, and 70+ more free tools.
Usman has 10+ years of experience securing enterprise infrastructure, managing high-traffic Magento stores, and building zero-knowledge security tools. Read more about the author.