— Technical
Why Magento cron jobs pile up (and what to actually do about it)
13 May 2026 · 6 min read
When a Magento 2 store’s cron table starts filling with pending jobs, the common assumption is that the cron daemon stopped firing. Almost always, that’s not the case.
The daemon is running. The cron_schedule table is just full of jobs stuck in running or pending state, and new jobs can’t get scheduled because Magento’s scheduler sees the queue as saturated. The daemon is spinning, scheduling nothing, processing nothing.
Here’s how to actually diagnose it.
How Magento’s cron model works
Magento runs two cron groups by default: default and index. Each group has its own scheduler and pool size. The scheduler fires on a system cron tick, looks at the cron_schedule table, and tries to claim pending jobs by setting their status to running. It then forks a process to execute each job.
The relevant config lives in app/etc/env.php or cron_groups.xml — specifically max_messages and max_pending_messages for queue consumers, and use_separate_process for the cron runner itself.
What trips most systems is the interaction between max_pending_messages and how Magento calculates “is the queue full?” If the scheduler sees more than N pending jobs for a group, it stops scheduling new ones. This is intended as a safety valve. It becomes a problem when some jobs are stuck in running state indefinitely — they count against the pending limit even though they’re not making progress.
The diagnostic queries
First, get a count by status:
SELECT status, COUNT(*) as count
FROM cron_schedule
GROUP BY status
ORDER BY count DESC;
If you see hundreds of running jobs, look at when they were created:
SELECT job_code, status, created_at, scheduled_at, executed_at
FROM cron_schedule
WHERE status = 'running'
ORDER BY executed_at ASC
LIMIT 20;
Jobs that have been running for more than a few minutes are almost certainly dead processes. The next question is why they died without updating their status.
The three common failure modes
Silent PHP fatal errors. A job throws an exception that PHP catches internally but doesn’t surface to Magento’s cron runner. The executed_at is set, the job is marked running, the process exits, and nothing updates the status to error. This is the most common cause.
Check your PHP error log — not the Magento exception log, the raw PHP error log — for fatal errors timestamped around when those jobs started.
Memory limit exits. The job hits memory_limit and PHP hard-exits. Same outcome: status stays running. You’ll see a Allowed memory size exhausted line in the PHP error log. The fix is usually not increasing the limit — it’s finding the memory leak. Magento’s indexers are the usual suspects.
Database deadlocks. A job acquires a table lock, another process is holding a conflicting lock, and the job times out waiting. MySQL will kill one of the transactions. Again: status stays running. Look at the MySQL error log and check SHOW ENGINE INNODB STATUS\G for deadlock history.
The cleanup
Once you know why jobs are dying, fix the root cause first. Then clean up the table:
-- Mark all stuck running jobs as error so the scheduler can proceed
UPDATE cron_schedule
SET status = 'error', messages = 'Manually cleaned — process did not complete'
WHERE status = 'running'
AND executed_at < DATE_SUB(NOW(), INTERVAL 10 MINUTE);
-- Remove old completed/error records beyond the retention window
DELETE FROM cron_schedule
WHERE status IN ('success', 'error', 'missed')
AND finished_at < DATE_SUB(NOW(), INTERVAL 7 DAY);
After this, the scheduler should start processing the pending queue again. Watch the cron_schedule table for a few minutes to confirm jobs are transitioning from pending → running → success.
The monitoring you should have had
Set up an alert on this query:
SELECT COUNT(*) FROM cron_schedule
WHERE status = 'running'
AND executed_at < DATE_SUB(NOW(), INTERVAL 15 MINUTE);
If that count is greater than 0, you have stuck jobs. This query should run on a 5-minute interval from your monitoring system. It’s cheap, it’s fast, and it catches this class of problem before users report it.
The cron_schedule table is one of those Magento internals that most teams never look at until something goes wrong. After you’ve debugged it once, you’ll add it to your regular health checks.
Savan Padaliya
Senior Engineering Consultant