This migration was not a staging exercise. It was a live LMS with active students and no tolerance for data loss.
The core lesson: Moodle migrations fail when teams treat them like simple file transfers. Moodle works only when codebase, database, moodledata, and runtime environment are aligned.
For teams modernizing legacy stacks, this is the same discipline we apply in larger migration projects: environment parity first, DNS cutover last.
TL;DR Checklist
- Clone code to target server.
- Install matching PHP extensions before first page load.
- Import database with correct credentials and prefix.
- Copy
moodledatawith correct permissions. - Update
config.phppaths and DB values. - Run CLI upgrade.
- Purge caches.
- Test logins, enrollments, uploads, and cron.
- Perform final DB +
moodledatasync at cutover. - Switch DNS only after verification.
Why This Migration Was Different
Legacy shared hosting hid too much infrastructure detail. We moved to a VPS stack with full control:
- Ubuntu
- CloudPanel
- MariaDB 10.6+
- PHP 8.2
- Moodle 4.5
This unlocked faster cron throughput, better response times, and observability across PHP-FPM + database layers.
The Golden Rule: Moodle Is a Four-Layer System
A Moodle site is healthy only when these four layers match:
- Codebase
- Database
moodledata- Server environment (PHP version, extensions, permissions, web server)
Break one and you can trigger HTTP 500, missing assets, enrollment bugs, or blank course pages.
Step-by-Step Migration Flow
Step 1: Export and import database
# source server
mysqldump -u USER -p DBNAME > moodle.sql
# target server
mysql -u USER -p DBNAME < moodle.sql
Match these with config.php exactly:
- DB host
- DB name
- DB user and password
- table prefix (
$CFG->prefix)
Step 2: Move moodledata correctly
rsync -avz /path/to/moodledata/ /newserver/moodledata/
chown -R www-data:www-data /newserver/moodledata
chmod -R 755 /newserver/moodledata
If moodledata is incomplete or permissions are wrong, you'll see missing files, course media issues, and inconsistent theme rendering.
Step 3: Fix config.php before first web request
$CFG->wwwroot = 'https://yourdomain.com';
$CFG->dataroot = '/home/user/moodledata';
$CFG->dbname = 'dbname';
$CFG->dbuser = 'dbuser';
$CFG->dbpass = 'dbpass';
Wrong paths or stale credentials are a top reason for migration-time HTTP 500 errors.
Step 4: Install PHP extensions before first load
apt install -y \
php8.2-xml php8.2-mysql php8.2-gd php8.2-curl \
php8.2-zip php8.2-mbstring php8.2-intl php8.2-soap
systemctl restart php8.2-fpm
Do this before opening Moodle in a browser.
Step 5: Run CLI upgrade and cache purge
php admin/cli/upgrade.php
php admin/cli/purge_caches.php
CLI first. Browser upgrade as a first attempt during migration is avoidable risk.
Step 6: Validate core production paths
- Student and admin login
- Course list and enrollment visibility
- File upload and media playback
- Theme consistency
- Cron execution (
php admin/cli/cron.php)
The Biggest Failures We Saw (And Their Root Cause)
HTTP 500 after migration
Common root causes:
- bad
dataroot - database mismatch in
config.php - PHP extension gaps
- permission errors on moodledata
Fix by tracing logs and config line-by-line, not by random plugin toggling.
Missing courses or broken media
Common root causes:
- partial
moodledatasync - wrong ownership on storage directories
- stale cache after theme/plugin changes
Fix sequence: resync -> fix ownership -> purge caches -> rerun upgrade.
Theme missing after restore
Common root causes:
- theme code not copied
- wrong plugin path
- cache not purged
Deploy exact theme code and rerun CLI maintenance commands.
Zero-Data-Loss Cutover Strategy
For live systems, students keep using the old server during staging buildout. To prevent data loss:
- Freeze a short cutover window.
- Run final source export and import.
- Run final
moodledatasync. - Verify critical flows.
- Only then switch DNS.
mysqldump -u USER -p live_db > final.sql
mysql -u USER -p new_db < final.sql
rsync -avz /old/moodledata/ /new/moodledata/
Featured Snippet Answers
What causes Moodle HTTP 500 after migration?
Incorrect config.php paths, DB credential mismatches, missing PHP extensions, and bad moodledata permissions are the most common causes.
What must be migrated together in Moodle?
Codebase, database, moodledata, and server environment must remain aligned.
What is the safest migration approach?
Build and validate target infrastructure privately, run final data sync at cutover, then switch DNS after confirmation.
Short Tutorial Version
If you want the compressed implementation checklist, use: Moodle migration zero-downtime checklist.
If you need migration execution support for PHP/WordPress-era stacks and custom platforms, start with our engineering services or contact us.