How to Set Up and Troubleshoot Cron Jobs in Magento 2
Cron jobs are essential for Magento 2 operations. They handle indexing, email sending, sitemap generation, currency rate updates, catalog price rules, and many other background tasks. A misconfigured or broken cron setup is one of the most common root causes behind issues that appear unrelated to cron at first glance.
Setting Up the Crontab
Magento 2 uses multiple cron groups, and each group requires its own crontab entry. Missing a group means tasks assigned to that group will never execute.
The standard cron groups include:
default-- general tasks (email, currency rates, notifications)index-- indexer-related tasks- Other groups added by extensions (e.g.,
cache_warmer,sitemap, etc.)
Crontab Configuration
Open the crontab for the web server user (usually www-data, apache, or nginx):
crontab -u www-data -e
Add an entry for each cron group:
* * * * * cd /var/www/magento2 && php bin/magento cron:run --group=default >> var/log/magento-default.log 2>&1
* * * * * cd /var/www/magento2 && php bin/magento cron:run --group=index >> var/log/magento-index.log 2>&1
If third-party extensions register additional cron groups (check crontab.xml files in extension modules), add entries for those groups as well.
Alternatively, you can run all groups with a single entry:
* * * * * cd /var/www/magento2 && php bin/magento cron:run >> var/log/magento-cron.log 2>&1
This iterates through all registered groups. The per-group approach gives better logging and control, while the single-entry approach is simpler to maintain.
Verifying Cron Is Running
Check the cron_schedule table to confirm jobs are being scheduled and executed:
php bin/magento cron:run
SELECT job_code, status, scheduled_at, executed_at, finished_at
FROM cron_schedule
ORDER BY scheduled_at DESC
LIMIT 20;
Status values: pending (scheduled), running (in progress), success (completed), error (failed), missed (skipped because a previous run was still active).
Troubleshooting Common Issues
Stuck or Frozen Cron Jobs
Jobs stuck in running or pending status block subsequent executions of the same job. This commonly happens after a server restart, PHP crash, or timeout during execution.
Solution: Clean up stuck entries:
-- Find stuck jobs
SELECT * FROM cron_schedule WHERE status = 'running' AND executed_at < DATE_SUB(NOW(), INTERVAL 2 HOUR);
-- Clear stuck jobs
DELETE FROM cron_schedule WHERE status = 'running' AND executed_at < DATE_SUB(NOW(), INTERVAL 2 HOUR);
-- Also clear very old pending jobs
DELETE FROM cron_schedule WHERE status = 'pending' AND scheduled_at < DATE_SUB(NOW(), INTERVAL 1 DAY);
After cleanup, run php bin/magento cron:run to reschedule tasks.
File Permission Issues
When cron runs under a different OS user than the web server, generated files (cache, logs, static content) may have incorrect ownership. This causes "Permission denied" errors in both cron and web contexts.
Solution: Ensure the cron user matches the web server user:
# Check current crontab user
crontab -l -u www-data
# Check web server user
ps aux | grep -E 'nginx|apache|php-fpm' | head -5
Both should be the same user. If they differ, move the crontab to the correct user or adjust file permissions accordingly.
See also: Magento 2 Correct Permissions
Heavy Indexing Tasks
Large catalogs with complex pricing rules generate significant cron workload. For example, a store with 225,000 products and 12 catalog price rules can require over 8,000 seconds for a full price reindex.
Recommendations:
- Schedule heavy reindexing during off-peak hours using Magento's cron schedule configuration
- Use "Update on Schedule" mode for indexers instead of "Update on Save" for large catalogs
- Monitor execution time in the
cron_scheduletable:SELECT job_code, TIMESTAMPDIFF(SECOND, executed_at, finished_at) as duration FROM cron_schedule WHERE status = 'success' ORDER BY duration DESC LIMIT 10;
Cron Schedule Table Maintenance
The cron_schedule table grows continuously. Magento has built-in cleanup settings under Stores → Configuration → Advanced → System → Cron:
- Schedule Ahead for: How far in advance to create schedule records (default: 20 minutes)
- Schedule Lifetime: How long a missed job remains valid (default: 15 minutes)
- History Cleanup Every: How often old records are purged (default: 10 minutes)
- Success History Lifetime: How long completed records are kept (default: 60 minutes)
- Failure History Lifetime: How long failed records are kept (default: 600 minutes)
If the table still grows too large, manually truncate it:
TRUNCATE TABLE cron_schedule;
This is safe to run -- Magento will recreate schedule entries on the next cron execution.
Checking Extension Cron Groups
To find all cron groups registered in your Magento installation:
find app/code vendor -name "crontab.xml" -exec grep -l "group=" {} \;
Each unique group value in those files should have a corresponding crontab entry if you use per-group scheduling.