Skip to content
Back to Insights
Engineering 4 min read

The Real-World Moodle Migration Playbook: From HTTP 500s to Production Stability

R

Roomi Kh

February 18, 2026

The Real-World Moodle Migration Playbook: From HTTP 500s to Production Stability

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

  1. Clone code to target server.
  2. Install matching PHP extensions before first page load.
  3. Import database with correct credentials and prefix.
  4. Copy moodledata with correct permissions.
  5. Update config.php paths and DB values.
  6. Run CLI upgrade.
  7. Purge caches.
  8. Test logins, enrollments, uploads, and cron.
  9. Perform final DB + moodledata sync at cutover.
  10. 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:

  1. Codebase
  2. Database
  3. moodledata
  4. 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

BASH
# 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

BASH
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

PHP
$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

BASH
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

BASH
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 moodledata sync
  • 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:

  1. Freeze a short cutover window.
  2. Run final source export and import.
  3. Run final moodledata sync.
  4. Verify critical flows.
  5. Only then switch DNS.
BASH
mysqldump -u USER -p live_db > final.sql
mysql -u USER -p new_db < final.sql
rsync -avz /old/moodledata/ /new/moodledata/

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.

Need a deeper implementation guide?