Moodle Admin · Server Config · Upgrade Guide 2026
⚡ Quick Answer
To upgrade Moodle safely: back up your database and moodledata, put the site in maintenance mode, replace the Moodle codebase, run the CLI upgrade script, then verify. Moodle 5.0 introduces a critical structural change — the web root now points to a public/ subdirectory, which requires updating your nginx or Apache config before the upgrade completes successfully.
Upgrading Moodle is one of those tasks that looks straightforward until it isn’t. A skipped step, a missed nginx config update, or an overlooked plugin compatibility check can take your LMS offline mid-upgrade — and recovering from that mid-flight is significantly harder than doing the pre-work correctly.
This guide covers every step in order, including the structural change introduced in Moodle 5.0 that catches most admins off guard: the move to a public/ subdirectory as the web root. If you’re upgrading to 5.0 or later and your nginx config still points to the old Moodle root, your site will return a blank page or 404 after the upgrade — even if everything else was done correctly.
Before You Start — Understanding the Upgrade Path
Moodle does not support skipping major versions. You must upgrade sequentially through each major release. The current supported upgrade path to reach Moodle 5.x is:
LTS = Long Term Support. If you’re on 3.9 or earlier, plan for multiple upgrade windows.
Check your current version at Site Administration → Notifications. If you’re more than one major version behind, block out multiple maintenance windows — attempting to jump from 3.9 to 5.0 in a single session is not supported and will corrupt your database.
What Changed in Moodle 5.0 — The Public Folder Structure
The most significant infrastructure change in Moodle 5.0 is the introduction of a public/ subdirectory as the web-accessible root. This brings Moodle’s directory layout closer to frameworks like Laravel — keeping sensitive files (config, CLI scripts, data directories) outside the publicly accessible path.
Old structure (Moodle 4.x and earlier)
/var/www/html/moodle/ ← WEB ROOT (nginx points here)
├── config.php
├── index.php
├── admin/
├── course/
├── lib/
├── mod/
└── theme/
/var/moodledata/ ← moodledata (outside web root ✓)
New structure (Moodle 5.0+)
/var/www/html/moodle/ ← Moodle root (NOT the web root)
├── config.php ← stays here, outside public/
├── admin/ ← CLI scripts live here
│ └── cli/
├── public/ ← WEB ROOT (nginx must point here)
│ ├── index.php
│ ├── course/
│ ├── lib/
│ ├── mod/
│ └── theme/
└── ...
/var/moodledata/ ← moodledata (outside web root ✓)
The key implication: after a 5.0 upgrade, your nginx root directive must point to /var/www/html/moodle/public — not /var/www/html/moodle. Forgetting this is the single most common reason Moodle 5.0 upgrades appear to fail when everything else was done correctly.
Phase 1: Pre-Upgrade Checklist
Complete every item on this list before touching any files. Skipping pre-checks is how planned 30-minute upgrades become 6-hour recovery exercises.
1. Check PHP version compatibility
Moodle 5.0 requires PHP 8.2 minimum (PHP 8.3 recommended). Check your current PHP version and the target Moodle version’s requirements before starting.
php -v # Must show 8.2.x or higher for Moodle 5.0 # Check required PHP extensions php -m | grep -E "curl|gd|intl|mbstring|openssl|soap|xml|zip"
If you’re on PHP 7.4 or PHP 8.0, upgrade PHP first — before upgrading Moodle. PHP upgrades on a live Moodle instance are their own procedure; do not attempt both in the same window.
2. Check all plugin compatibility
This step is non-negotiable. Go to Site Administration → Plugins → Plugins overview. Check the version of every installed plugin against the Moodle Plugins Directory for compatibility with your target Moodle version. Incompatible plugins will block the upgrade or cause errors post-upgrade.
For each incompatible plugin, decide: update it, disable it before upgrading and re-enable after, or remove it. Do not proceed with a plugin installed that has no compatible version for your target release.
3. Check server resources
# Disk space — Moodle codebase + backup needs at minimum 2x current size free
df -h /var/www/html
# Memory — upgrade needs at least 512MB PHP memory_limit
php -r "echo ini_get('memory_limit');"
# MySQL/PostgreSQL version
mysql --version # MySQL 8.0+ required for Moodle 5.0
psql --version # PostgreSQL 13+ required
4. Review the Moodle release notes
Read the release notes for every version between your current version and target version. Pay particular attention to database schema changes, deprecated functions used by your custom code, and any required configuration changes. The Moodle docs at docs.moodle.org are the authoritative source — not third-party summaries.
Phase 2: Backup Everything
There is no such thing as too many backups before a Moodle upgrade. You need three independent backups: the database, the moodledata directory, and the Moodle codebase. All three are required for a full recovery if something goes wrong.
Database backup
# MySQL mysqldump -u moodleuser -p moodledb > /backup/moodle_db_$(date +%Y%m%d_%H%M).sql # Compress it immediately — Moodle databases are large gzip /backup/moodle_db_$(date +%Y%m%d_%H%M).sql # PostgreSQL pg_dump -U moodleuser moodledb | gzip > /backup/moodle_db_$(date +%Y%m%d_%H%M).sql.gz # Verify the backup is not zero bytes ls -lh /backup/moodle_db_*.sql.gz
Moodledata backup
# Moodledata — where all uploaded files, course backups, and sessions live tar -czf /backup/moodledata_$(date +%Y%m%d_%H%M).tar.gz /var/moodledata/ # This can be large (10GB+ on busy sites) — run in screen/tmux screen -S moodle_backup tar -czf /backup/moodledata_$(date +%Y%m%d_%H%M).tar.gz /var/moodledata/
Codebase backup (including config.php)
# Back up entire Moodle directory — preserves config.php and any local customisations tar -czf /backup/moodle_code_$(date +%Y%m%d_%H%M).tar.gz /var/www/html/moodle/ # CRITICAL: separately back up config.php on its own cp /var/www/html/moodle/config.php /backup/config.php.bak
Once all three backups are complete and verified, move copies off the server — to S3, a separate NFS mount, or your local machine. A backup that lives only on the same server as the site you’re upgrading is not a real backup.
Phase 3: Enable Maintenance Mode
# Enable via CLI (recommended — works even if web UI is unavailable) sudo -u www-data php /var/www/html/moodle/admin/cli/maintenance.php --enable # Verify it's on sudo -u www-data php /var/www/html/moodle/admin/cli/maintenance.php --status # Purge all caches before starting sudo -u www-data php /var/www/html/moodle/admin/cli/purge_caches.php
Do not proceed until maintenance mode is confirmed active. Active sessions during an upgrade can cause data corruption and partial upgrades that are genuinely difficult to recover from.
Phase 4: Replace the Moodle Codebase
There are two approaches: direct download + replace, or git pull if your Moodle instance is managed via git. The git approach is faster and easier to roll back — use it if your setup supports it.
Option A: Download and replace (standard)
# Download the new version to /tmp cd /tmp wget https://download.moodle.org/download.php/direct/stable500/moodle-latest-500.tgz # Replace 500 with your target version number # Extract tar -xzf moodle-latest-500.tgz # This creates /tmp/moodle/ # Move old Moodle out of the way (do NOT delete it yet) mv /var/www/html/moodle /var/www/html/moodle_old_$(date +%Y%m%d) # Move new Moodle into place mv /tmp/moodle /var/www/html/moodle # Restore your config.php — CRITICAL STEP cp /backup/config.php.bak /var/www/html/moodle/config.php # Restore any custom local plugins from the old codebase # cp -r /var/www/html/moodle_old_YYYYMMDD/local/ /var/www/html/moodle/local/ # cp -r /var/www/html/moodle_old_YYYYMMDD/theme/yourtheme/ /var/www/html/moodle/theme/ # Fix ownership chown -R www-data:www-data /var/www/html/moodle/ chmod -R 755 /var/www/html/moodle/
Option B: Git upgrade (recommended for managed deployments)
cd /var/www/html/moodle # Fetch all remote tags and branches git fetch --all # Check available release tags git tag | grep "v5." | sort -V | tail -20 # Switch to the target release branch git checkout MOODLE_500_STABLE # or a specific tag: git checkout v5.0.1 # Verify the checkout git log --oneline -3 # Fix ownership after checkout chown -R www-data:www-data /var/www/html/moodle/
Update config.php for Moodle 5.0 (if upgrading from 4.x)
When upgrading to Moodle 5.0, your existing config.php will continue to work — it is placed in the Moodle root (parent of public/), which is correct. However, verify these two directives are set correctly:
// In config.php — verify these values after 5.0 upgrade $CFG->wwwroot = 'https://yourmoodle.com'; // no trailing slash $CFG->dirroot = '/var/www/html/moodle'; // Moodle ROOT, not public/ $CFG->dataroot = '/var/moodledata'; // outside web root $CFG->admin = 'admin'; // admin directory name // For Moodle 5.0 public folder — this is auto-detected // but can be explicitly set if needed: // $CFG->publicroot = $CFG->dirroot . '/public';
Phase 5: Update Nginx for the Moodle 5.0 Public Folder
This is the step that breaks most Moodle 5.0 upgrades if skipped. Your nginx config must point its root directive to the public/ subdirectory, not the Moodle root.
Full nginx config for Moodle 5.0 (HTTPS)
server { listen 80; server_name yourmoodle.com www.yourmoodle.com; return 301 https://$host$request_uri; } server { listen 443 ssl http2; server_name yourmoodle.com www.yourmoodle.com; # --- SSL --- ssl_certificate /etc/letsencrypt/live/yourmoodle.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/yourmoodle.com/privkey.pem; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; ssl_session_cache shared:SSL:10m; ssl_session_timeout 1d; # --- CRITICAL: point to public/ subdirectory for Moodle 5.0 --- root /var/www/html/moodle/public; index index.php index.html; client_max_body_size 500M; # --- Security headers --- add_header X-Frame-Options "SAMEORIGIN" always; add_header X-Content-Type-Options "nosniff" always; add_header X-XSS-Protection "1; mode=block" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; # --- Protect sensitive files --- location ~* /(.|config.php|version.php|upgrade.txt) { deny all; return 404; } # --- Block access to moodledata if accidentally inside web root --- location /dataroot/ { deny all; return 404; } # --- Static files --- location ~* .(jpg|jpeg|png|gif|ico|css|js|woff|woff2|svg|ttf|eot)$ { expires 30d; add_header Cache-Control "public, immutable"; try_files $uri =404; } # --- PHP handling --- location ~ .php$ { try_files $uri =404; fastcgi_split_path_info ^(.+.php)(/.+)$; fastcgi_pass unix:/run/php/php8.2-fpm.sock; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param PATH_INFO $fastcgi_path_info; include fastcgi_params; fastcgi_read_timeout 300; fastcgi_buffers 16 16k; fastcgi_buffer_size 32k; } # --- Main location --- location / { try_files $uri $uri/ /index.php?$query_string; } # --- Logging --- access_log /var/log/nginx/moodle_access.log; error_log /var/log/nginx/moodle_error.log; }
Test and reload nginx
# Always test config before reloading — a syntax error takes nginx down nginx -t # If test passes: systemctl reload nginx # Verify nginx is running systemctl status nginx
Apache config for Moodle 5.0 (if using Apache instead of nginx)
<VirtualHost *:443> ServerName yourmoodle.com # Point DocumentRoot to public/ for Moodle 5.0 DocumentRoot /var/www/html/moodle/public <Directory /var/www/html/moodle/public> Options -Indexes +FollowSymLinks AllowOverride All Require all granted </Directory> SSLEngine on SSLCertificateFile /etc/letsencrypt/live/yourmoodle.com/fullchain.pem SSLCertificateKeyFile /etc/letsencrypt/live/yourmoodle.com/privkey.pem ErrorLog ${APACHE_LOG_DIR}/moodle_error.log CustomLog ${APACHE_LOG_DIR}/moodle_access.log combined </VirtualHost>
Phase 6: Run the CLI Upgrade
The CLI upgrade is the most reliable upgrade method for production sites. Never run the upgrade through the web browser on a large database — the PHP execution time limits and browser timeout will kill the process mid-way through database schema updates.
# Run inside screen or tmux — upgrade can take 20-60 minutes on large databases screen -S moodle_upgrade # Run the upgrade — note: in Moodle 5.0, admin CLI is in the root, not public/ sudo -u www-data php /var/www/html/moodle/admin/cli/upgrade.php # The script will: # 1. Check current version vs new version # 2. List all pending database upgrades # 3. Ask for confirmation — type 'yes' to proceed # 4. Run all upgrade steps in sequence # 5. Report completion or any errors # Watch for: "Upgrade complete" at the end # Any error message during upgrade — do NOT ignore, investigate immediately
If the upgrade script reports errors on specific plugins — typically “plugin X has no upgrade function for version Y” — these are usually safe to acknowledge and continue if the plugin is a third-party plugin that will be updated separately. However, errors on Moodle core components must be investigated before continuing.
Disable maintenance mode after upgrade
# Only disable after CLI upgrade reports successful completion sudo -u www-data php /var/www/html/moodle/admin/cli/maintenance.php --disable # Purge all caches post-upgrade sudo -u www-data php /var/www/html/moodle/admin/cli/purge_caches.php
Phase 7: Post-Upgrade Verification
Do not declare the upgrade complete until you’ve verified every item in this list. Partial upgrade failures often don’t surface immediately — they appear as errors on specific course pages, plugin settings, or report views hours or days later.
Immediate checks (do these within 5 minutes of going live)
- Homepage loads — visit your Moodle URL and verify the login page appears correctly.
- Admin login works — log in as admin, confirm dashboard loads without PHP errors.
- Site Administration → Notifications — should show no pending upgrades and confirm current version.
- Check for PHP errors —
tail -f /var/log/nginx/moodle_error.logwhile clicking around the site. Any PHP warnings or fatal errors must be addressed immediately. - Test a course — open a course, click through to an activity, confirm content renders correctly.
- Test file upload — upload a small test file to a course. Verify it saves to moodledata correctly.
Full verification checks (complete within 24 hours)
# Check all plugins are updated and active # Site Administration → Plugins → Plugins overview # Look for: "Update available" or "Missing from disk" warnings # Verify cron is running post-upgrade sudo -u www-data php /var/www/html/moodle/admin/cli/cron.php # Should complete without errors # Run environment check # Site Administration → Server → Environment # All items should show green / OK # Check scheduled tasks # Site Administration → Server → Scheduled tasks # No tasks should show errors from the upgrade window # Verify moodledata permissions ls -la /var/moodledata/ # Should be owned by www-data, not root # Test email delivery (Moodle sends a test email) # Site Administration → Server → Email → Outgoing mail configuration → Test outgoing mail
Common Upgrade Errors and How to Fix Them
Error: “Site is in maintenance mode”
If you see this after disabling maintenance mode, the maintenance mode flag file is still present. Remove it manually:
rm /var/moodledata/climaintenance.html # Then reload the page
Error: Blank page or 404 after Moodle 5.0 upgrade
This is almost always the nginx/Apache root still pointing to the old Moodle root instead of public/. Check your server config and update the root directive as shown in Phase 5.
# Quick diagnostic: does the public folder exist? ls /var/www/html/moodle/public/index.php # If this returns "No such file" — your codebase replacement failed # Check nginx is pointing to the right root grep -r "root " /etc/nginx/sites-enabled/moodle.conf
Error: “Database connection failed” after upgrade
Your config.php was not copied to the new codebase location, or the database credentials have changed. Verify:
cat /var/www/html/moodle/config.php | grep -E "dbhost|dbname|dbuser" # Verify these match your actual database credentials # Test database connection manually mysql -u moodleuser -p moodledb -e "SELECT COUNT(*) FROM mdl_course;"
Error: “The following plugins are missing from disk”
Custom plugins, themes, or local plugins from your old codebase were not copied to the new installation. Copy them from your old codebase backup:
cp -r /var/www/html/moodle_old_YYYYMMDD/local/yourplugin/
/var/www/html/moodle/local/
cp -r /var/www/html/moodle_old_YYYYMMDD/theme/yourtheme/
/var/www/html/moodle/theme/
chown -R www-data:www-data /var/www/html/moodle/local/ /var/www/html/moodle/theme/
sudo -u www-data php /var/www/html/moodle/admin/cli/upgrade.php
Error: Cron not running / scheduled tasks failing
# Check crontab — path may need updating for Moodle 5.0 structure crontab -l -u www-data # Standard Moodle cron entry (update path if needed) # */1 * * * * /usr/bin/php /var/www/html/moodle/admin/cli/cron.php > /dev/null 2>&1 # For Moodle 5.0 — cron script path unchanged (admin/cli/ is in root, not public/) # No change needed to crontab for 5.0 upgrade
Rolling Back If Something Goes Wrong
If the upgrade fails and you need to roll back, the process is the reverse of the upgrade — but only works cleanly if your backups are intact and the database has not been partially upgraded.
# 1. Enable maintenance mode immediately on the broken site (if possible) sudo -u www-data php /var/www/html/moodle/admin/cli/maintenance.php --enable # 2. Restore the old codebase mv /var/www/html/moodle /var/www/html/moodle_failed mv /var/www/html/moodle_old_YYYYMMDD /var/www/html/moodle # 3. Restore the database from backup mysql -u moodleuser -p moodledb < /backup/moodle_db_YYYYMMDD_HHMM.sql # or for PostgreSQL: # gunzip -c /backup/moodle_db_YYYYMMDD_HHMM.sql.gz | psql -U moodleuser moodledb # 4. Restore nginx config to point to old root (not public/) # Edit /etc/nginx/sites-enabled/moodle.conf and revert root directive nginx -t && systemctl reload nginx # 5. Disable maintenance mode sudo -u www-data php /var/www/html/moodle/admin/cli/maintenance.php --disable
A partial database upgrade (CLI was killed mid-way) is the hardest failure scenario. If you can’t roll back the database cleanly, engage a Moodle specialist — attempting to manually patch a partially upgraded Moodle database without deep schema knowledge typically causes more damage than the original failure.
Upgrade Checklist — Quick Reference
| Phase | Step | Critical for 5.0? |
|---|---|---|
| Pre-upgrade | Check PHP version (8.2+ for 5.0) | ✅ Yes |
| Pre-upgrade | Check all plugin compatibility | ✅ Yes |
| Pre-upgrade | Check upgrade path (no version skipping) | ✅ Yes |
| Backup | Database dump (compressed) | ✅ Yes |
| Backup | Moodledata directory | ✅ Yes |
| Backup | config.php separately | ✅ Yes |
| Pre-upgrade | Enable maintenance mode via CLI | ✅ Yes |
| Upgrade | Replace codebase + restore config.php | ✅ Yes |
| Server config | Update nginx root → /moodle/public | 🔴 5.0 only |
| Upgrade | Run CLI upgrade script | ✅ Yes |
| Post-upgrade | Purge caches | ✅ Yes |
| Post-upgrade | Disable maintenance mode | ✅ Yes |
| Post-upgrade | Verify site, admin login, course access | ✅ Yes |
| Post-upgrade | Check cron and scheduled tasks | ✅ Yes |
| Post-upgrade | Update all third-party plugins | ✅ Yes |
Need Help with Your Moodle Upgrade?
Moodle upgrades on production systems with large databases, custom themes, and complex plugin stacks require careful planning and execution. At EdzLMS, our DevOps team has handled Moodle upgrades across 50+ organisations — from small institutes on shared hosting to enterprise deployments running 100,000+ learners on dedicated infrastructure.
If you’re upgrading to Moodle 5.0 and need support with the directory structure migration, nginx reconfiguration, plugin compatibility audit, or want the upgrade performed with zero-downtime on a staged environment, our team can manage the entire process. We also handle post-upgrade theme migration — if you’re running a custom Boost child theme or a commercial theme that needs updating for 5.0, see our guide to the best Moodle themes for 2026 for compatible options.
For organisations considering an upgrade as an opportunity to move to an AI-enhanced managed Moodle deployment, see how managed Moodle with AXIS AI eliminates upgrade overhead entirely — your infrastructure, your data, but with a team handling every version update, plugin audit, and server configuration change on your behalf.
Related reading: Best Moodle Plugins for eLearning · Best Moodle Themes 2026 · Moodle Deployment Options · Moodle for Corporate Training India