Diagnosing Moodle Cron and Scheduled Task Failures
A step‑by‑step diagnostic guide for Moodle cron and scheduled task failures: recognize stuck runs, map symptoms to root causes, run ordered checks, apply targeted fixes, and know when to escalate.
21 Aug 2026, 03:48 UTC

Recognizable Condition
Moodle's Scheduled Tasks page shows Last run timestamps older than the configured interval, Next run does not advance, and the admin notification "Cron has not run for 24 hours" appears. Plugins such as enrol_ldap report stale data, yet no PHP errors surface in the web UI. Background jobs pile up silently.
Quick Diagnostic Table
| Symptom | Likely Cause | Quick Test |
|---|---|---|
| All tasks stuck, Last run frozen | Cron not triggered at OS level | ps aux | grep cron; systemctl list-timers moodle-cron* |
| Single task fails repeatedly, others run | Task throws exception or times out | Check mdl_task_adhoc / mdl_task_scheduled for lastruntime = 0 or faildelay > 0; enable debug logging |
| Cron running lock persists > 1 h | Previous run crashed holding lock | SELECT * FROM mdl_config WHERE name='cron_running'; returns value = 1 |
| Tasks run but data not updated (e.g., enrolments) | Task logic error or external API failure | Run manually: php admin/cli/scheduled_task.php --execute='\enrol_ldap\task\sync' |
| Memory fatal error in logs | Task processes too many records | php -i | grep memory_limit |
| Database deadlock / lock wait timeout | Concurrent long tasks on same tables | SHOW ENGINE INNODB STATUS; look for LOCK WAIT on mdl_user_enrolments |
Ordered Verification Steps
- Verify OS trigger. On the Moodle application server, run:
Confirm the systemd timer (or crontab) fires every minute. Requires root or sudo access.journalctl -u moodle-cron -f - Run Moodle cron manually. As the web‑server user (e.g.,
www-data):
Watch for fatal errors, memory exhaustion, or timeout messages. This step changes no state; it only executes the current cron cycle.php /path/to/moodle/admin/cli/cron.php - Inspect the cron lock. In the Moodle database:
IfSELECT * FROM mdl_config WHERE name='cron_running';value = 1and no live cron process exists (pkill -f 'admin/cli/cron.php'returns nothing), clear the lock:
Risk: Clearing while a legitimate cron runs causes concurrent executions. Always verify withUPDATE mdl_config SET value=0 WHERE name='cron_running';ps aux | grep cron.phpfirst. - Check scheduled‑task tables.
Tasks withSELECT * FROM mdl_task_scheduled WHERE faildelay > 0 OR lastruntime = 0; SELECT * FROM mdl_task_adhoc WHERE faildelay > 0 OR lastruntime = 0;faildelay > 0are backing off;lastruntime = 0means they never ran. - Enable debug logging for a failing task. Temporarily set in
config.php:
Re‑run the specific task via CLI to capture a stack trace. Revert after diagnosis.$CFG->debug = E_ALL; $CFG->debugdisplay = true; - Validate PHP limits. Compare
max_execution_timeandmemory_limit(CLI php.ini) against the task's needs. Heavy tasks (e.g., backup, enrol sync) often require higher limits. - Review database long‑query logs during the cron window. Look for queries exceeding
innodb_lock_wait_timeout(default 50 s) on tables likemdl_user_enrolments.
Targeted Fixes
OS Timer Inactive
Enable the systemd unit or add a crontab entry for the web‑server user:
* * * * * www-data /usr/bin/php /var/www/moodle/admin/cli/cron.php > /dev/null
Run as root to install the crontab; the command executes as www-data.
Stuck cron_running Lock
After confirming no live cron process, run the SQL update shown in step 3. This changes state; rollback is the same update setting value=1 if you cleared erroneously.
Single Task Failing
- Code fix (preferred): Override the task in a local plugin, wrap the
execute()method in try/catch, log details, and return gracefully so the scheduler marks success. - CLI limits: Raise limits only for the cron command:
Avoid raising globally; it masks inefficient code.php -d memory_limit=512M -d max_execution_time=0 /path/to/moodle/admin/cli/cron.php
Memory Fatal Errors
Implement chunked processing inside the task using get_recordset_sql with limit/offset, or increase CLI memory_limit as above. Example chunk size: 500 records per iteration.
Database Deadlocks
- Stagger heavy tasks via custom schedules in
db/tasks.php(e.g., run enrol sync at 02:00, backup at 03:30). - Avoid
LOCK TABLES; use InnoDB row‑level locking. - Set
innodb_lock_wait_timeout ≥ 120in MySQL/MariaDB configuration.
External API Timeouts
Add retry with exponential backoff, a circuit‑breaker pattern, and alerting on repeated failures. Log the HTTP status and response body for later analysis.
Escalation Criteria
- Cron down > 2 hours (impacts enrolments, grades, reports) → page on‑call engineer.
- Critical task (auth, enrol, backup) failing > 3 consecutive runs → create incident ticket.
- DB locks causing site‑wide slowness during cron window → engage DBA.
- Recurring OOM kills despite chunking → architecture review.
- Security‑relevant tasks (e.g.,
core\task\session_cleanup) failing → treat as compliance risk, escalate to security team.
Cautions and Limitations
- Clearing
cron_runningwhile a legitimate cron process is alive causes concurrent runs — always verify withps auxfirst. - Raising PHP
memory_limitglobally masks inefficient tasks; prefer chunked iteration in task code. - Disabling a failing scheduled task via the Tasks UI stops its schedule but leaves
faildelay; resetfaildelayto 0 in the database if re‑enabling after a fix. - Moodle 4.x+ uses
task_adhocfor one‑off jobs; stuck adhoc tasks also block cron if they error — check both tables. - Systemd timers with
Persistent=truemay backfill missed runs on boot, causing a storm — monitor after reboots. - Third‑party plugins often ship tasks without proper exception handling; audit custom tasks before production deploy.
Verification Checklist
- Trigger cron manually and confirm output ends with
Cron script completed correctly. - Tasks page shows updated Last run for all tasks within the expected schedule window.
- Tail
moodledata/logs/for new error entries during the next 2–3 automated runs. - Run
php admin/cli/scheduled_task.php --listto verify no tasks show Disabled or Error status. - Set up monitoring: alert if
SELECT value FROM mdl_config WHERE name='cron_running';stays 1 > 10 min, or if any tasklastruntime< NOW() - 2 * schedule_interval.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.