Complete Tutorial: Upgrade Moodle to 5.2 on cPanel / Shared Hosting Step by Step

This tutorial explains how to upgrade an existing Moodle site to Moodle 5.2, especially on a cPanel server where the main domain document root cannot be changed.

This guide is based on a real upgrade scenario:

Domain: https://www.devopsschool.xyz
Moodle root: /home/devopsschoolxyz/public_html
Moodle data: /home/devopsschoolxyz/ds-moodledata
PHP version: ea-php83
Database: MariaDB/MySQL
Hosting: cPanel / Apache

Moodle 5.2 requires Moodle 4.4 or later as the upgrade source, PHP 8.3 or later, 64-bit PHP, and the sodium PHP extension. Moodle 5.1+ also introduced the new public/ webroot structure, meaning the web server should serve the public directory rather than the full Moodle application directory. (Moodle Developer Resources)


1. What changed in Moodle 5.2?

Moodle 5.2 is not just a normal file replacement upgrade. The important change is the new public directory layout.

Older Moodle versions usually worked like this:

/home/user/public_html/index.php
/home/user/public_html/config.php
/home/user/public_html/admin/
/home/user/public_html/lib/
/home/user/public_html/theme/

Moodle 5.2 works like this:

/home/user/public_html/          ← Moodle application root
/home/user/public_html/public/   ← web-accessible document root
/home/user/config.php            ← real Moodle config in some deployments

The root index.php in Moodle 5.2 intentionally blocks direct access outside the public webroot. This is expected behavior, not a broken package. Moodle documentation says Apache should point the document root to the public directory, and router support should be configured using FallbackResource /r.php when possible. (Moodle Docs)

On cPanel, the main domain often cannot be changed from:

/home/user/public_html

to:

/home/user/public_html/public

So we use an .htaccess routing workaround.


2. Before You Start

2.1 Check current Moodle version

cd ~/public_html
grep '$release' version.php
grep '$branch' version.php
grep '$version' version.php

Example old output:

$release  = '5.0.1 (Build: 20250609)';
$branch   = '500';

If your database is already newer than your code, Moodle may show:

ERROR!!! The code you are using is OLDER than the version that made these databases!

That means you must upgrade the Moodle files to match or exceed the database version.


2.2 Check database version from Moodle DB

First check DB name and prefix:

grep dbname config.php
grep prefix config.php

Example:

$CFG->dbname = 'devopsschoolxyz_lms';
$CFG->prefix = 'mdl_';

Then in phpMyAdmin, run:

SELECT name, value
FROM mdl_config
WHERE name IN ('version','release','branch');

Example:

branch  = 500
release = 5.0.1
version = 2025041401

If database says 5.0.1 but files say 4.2.5, you must install Moodle 5.0.1 or newer files.


3. Backup Everything

Do not skip this.

3.1 Backup Moodle code and config

cd ~
mkdir -p ~/moodle-backups

tar -czf ~/moodle-backups/moodle-files-before-52-$(date +%F-%H%M%S).tar.gz \
  ~/public_html \
  ~/config.php \
  ~/public_html/config.php \
  ~/public_html/.htaccess 2>/dev/null

3.2 Backup Moodle database

If your real config is in ~/config.php, use this command to read DB details from the config and create a dump:

/opt/cpanel/ea-php83/root/usr/bin/php -r '$c=file_get_contents(getenv("HOME")."/config.php"); foreach(["dbname","dbuser","dbpass"] as $k){ if(!preg_match("/\\$CFG->$k\s*=\s*'\''([^'\'']*)'\'';/",$c,$m)){fwrite(STDERR,"Missing $k\n"); exit(1);} $$k=$m[1]; } $out=getenv("HOME")."/moodle-db-".date("Ymd-His").".sql.gz"; $cmd="mysqldump --single-transaction --quick --routines --triggers -u".escapeshellarg($dbuser)." -p".escapeshellarg($dbpass)." ".escapeshellarg($dbname)." | gzip > ".escapeshellarg($out); passthru($cmd,$code); if($code===0){echo "DB dump created: $out\n";} else {echo "DB dump failed\n"; exit($code);}'

Expected output:

DB dump created: /home/devopsschoolxyz/moodle-db-20260503-193000.sql.gz

If your real config is still inside ~/public_html/config.php, change this part:

getenv("HOME")."/config.php"

to:

getenv("HOME")."/public_html/config.php"

4. Prepare PHP 8.3

Moodle 5.2 requires PHP 8.3 or newer. Moodle 5.2 also requires a modern PHP environment, including sodium; common Moodle extensions include mysqli, pdo_mysql, mbstring, intl, curl, zip, gd, soap, fileinfo, exif, iconv, XML extensions, and OpenSSL. (Moodle Developer Resources)

Check PHP version:

/opt/cpanel/ea-php83/root/usr/bin/php -v

Expected:

PHP 8.3.x

Check required PHP extensions:

/opt/cpanel/ea-php83/root/usr/bin/php -m | grep -Ei "mysqli|mysqlnd|pdo_mysql|iconv|intl|mbstring|xml|simplexml|dom|curl|zip|gd|soap|sodium|fileinfo|exif|openssl"

Good output example:

curl
dom
exif
fileinfo
gd
iconv
intl
mbstring
mysqli
mysqlnd
openssl
pdo_mysql
SimpleXML
soap
sodium
xml
xmlreader
xmlwriter
zip

If some extensions are missing, enable them in:

cPanel → Select PHP Version → Extensions

or:

cPanel → MultiPHP INI Editor

If you have WHM/root access, install EasyApache packages for PHP 8.3.


5. Set PHP Handler in .htaccess

Check current PHP handler:

grep -i "AddHandler" ~/public_html/.htaccess

For PHP 8.3, you want something like:

AddHandler application/x-httpd-ea-php83 .php .php8 .phtml

If needed, update .htaccess:

cat > ~/public_html/.htaccess <<'EOF'
# Moodle 5.2 public directory routing
<IfModule mod_rewrite.c>
  RewriteEngine On

  # Block Git files
  RewriteRule (^|/)\.git(/|$) - [F,L]
  RewriteRule (^|/)\.gitignore$ - [F,L]
  RewriteRule (^|/)\.gitmodules$ - [F,L]

  # Route all web requests to Moodle public directory
  RewriteRule ^$ public/ [L]
  RewriteRule ^((?!public/).*)$ public/$1 [L]
</IfModule>

# php -- BEGIN cPanel-generated handler, do not edit
<IfModule mime_module>
  AddHandler application/x-httpd-ea-php83 .php .php8 .phtml
</IfModule>
# php -- END cPanel-generated handler, do not edit
EOF

This is needed when cPanel does not allow changing the main domain document root to public_html/public.


6. Download Moodle 5.2

Download the Moodle 5.2 package outside public_html:

cd ~
rm -rf moodle moodle-5.2.tgz
wget -O moodle-5.2.tgz https://download.moodle.org/download.php/direct/stable502/moodle-latest-502.tgz
tar -xzf moodle-5.2.tgz

Verify package:

ls -la ~/moodle/version.php
grep '$release' ~/moodle/version.php
grep '$branch' ~/moodle/version.php

Expected:

$release = '5.2+ ...'
$branch  = '502'

7. Copy Moodle 5.2 Files Into Existing public_html

If your hosting has low memory, rsync may fail with:

rsync error: error allocating core memory buffers

Use tar streaming instead.

cd ~/moodle
tar --exclude='./config.php' --exclude='./.git' --exclude='./.htaccess' -cf - . | (cd ~/public_html && tar -xpf -)

Verify:

grep '$release' ~/public_html/version.php
grep '$branch' ~/public_html/version.php
ls ~/public_html/admin/cli/upgrade.php

Expected:

$release = '5.2+ ...'
$branch  = '502'
/home/devopsschoolxyz/public_html/admin/cli/upgrade.php

8. Understand the Moodle 5.2 Config File Layout

In Moodle 5.2, public_html/config.php may be a loader file that loads the real config from one directory above:

/home/devopsschoolxyz/config.php

Your real Moodle config should contain database settings and these paths:

$CFG->wwwroot   = 'https://www.devopsschool.xyz';
$CFG->dataroot  = '/home/devopsschoolxyz/ds-moodledata';
$CFG->dirroot   = '/home/devopsschoolxyz/public_html';
$CFG->libdir    = $CFG->dirroot . '/lib';
$CFG->admin     = 'admin';

require_once($CFG->dirroot . '/lib/setup.php');

Check real config:

nl -ba ~/config.php | sed -n '1,60p'

If dirroot is missing, add it:

grep -q "CFG->dirroot" ~/config.php || sed -i "/CFG->dataroot/a \$CFG->dirroot   = '/home/devopsschoolxyz/public_html';" ~/config.php

If libdir is missing, add it:

grep -q "CFG->libdir" ~/config.php || sed -i "/CFG->dirroot/a \$CFG->libdir    = \$CFG->dirroot . '/lib';" ~/config.php

Make sure require_once uses $CFG->dirroot:

grep "require_once" ~/config.php

Correct:

require_once($CFG->dirroot . '/lib/setup.php');

Wrong:

require_once(__DIR__ . '/lib/setup.php');

If wrong, fix:

sed -i "s#require_once(__DIR__ . '/lib/setup.php');#require_once(\$CFG->dirroot . '/lib/setup.php');#" ~/config.php

Now clean the loader file:

cat > ~/public_html/config.php <<'EOF'
<?php
$configfile = __DIR__ . '/../config.php';
if (!file_exists($configfile)) {
    header("Location: install.php");
    die;
}
require_once($configfile);
EOF

9. Configure Moodle 5.2 Public Webroot Routing

Best option

If you can change the domain document root, set it to:

/home/devopsschoolxyz/public_html/public

But on many cPanel main domains, this is not possible.

cPanel workaround

Keep this in:

/home/devopsschoolxyz/public_html/.htaccess
# Moodle 5.2 public directory routing
<IfModule mod_rewrite.c>
  RewriteEngine On

  # Block Git files
  RewriteRule (^|/)\.git(/|$) - [F,L]
  RewriteRule (^|/)\.gitignore$ - [F,L]
  RewriteRule (^|/)\.gitmodules$ - [F,L]

  # Route all web requests to Moodle public directory
  RewriteRule ^$ public/ [L]
  RewriteRule ^((?!public/).*)$ public/$1 [L]
</IfModule>

# php -- BEGIN cPanel-generated handler, do not edit
<IfModule mime_module>
  AddHandler application/x-httpd-ea-php83 .php .php8 .phtml
</IfModule>
# php -- END cPanel-generated handler, do not edit

Then configure router fallback inside:

/home/devopsschoolxyz/public_html/public/.htaccess
cat > ~/public_html/public/.htaccess <<'EOF'
DirectoryIndex index.php
FallbackResource /r.php
EOF

Add router flag in Moodle config:

grep -q "routerconfigured" ~/config.php || sed -i "/CFG->libdir/a \$CFG->routerconfigured = true;" ~/config.php

Moodle’s upgrade/router docs recommend FallbackResource /r.php for Apache routing; if you cannot configure Apache globally, Moodle says the fallback can be placed in an override file such as .htaccess where allowed. (Moodle Docs)


10. Run Moodle Upgrade

Run upgrade:

/opt/cpanel/ea-php83/root/usr/bin/php ~/public_html/admin/cli/upgrade.php

Possible good output:

No upgrade needed for the installed version 5.2+ ...

or it may run database upgrade steps.

Then purge Moodle caches:

/opt/cpanel/ea-php83/root/usr/bin/php ~/public_html/admin/cli/purge_caches.php

Clear file caches:

rm -rf /home/devopsschoolxyz/ds-moodledata/cache/*
rm -rf /home/devopsschoolxyz/ds-moodledata/localcache/*
rm -rf /home/devopsschoolxyz/ds-moodledata/temp/*

11. Fix max_input_vars

Moodle environment check may show:

PHP setting max_input_vars must be at least 5000.

Create .user.ini files:

cat > ~/public_html/.user.ini <<'EOF'
max_input_vars = 5000
EOF

cat > ~/public_html/public/.user.ini <<'EOF'
max_input_vars = 5000
EOF

Also set it in:

cPanel → MultiPHP INI Editor → devopsschool.xyz

Add:

max_input_vars = 5000

Verify CLI:

/opt/cpanel/ea-php83/root/usr/bin/php -i | grep max_input_vars

Expected:

max_input_vars => 5000 => 5000

Note: Moodle’s browser environment check uses web PHP, so cPanel MultiPHP INI Editor is usually the most reliable place to set it.


12. Fix Composer Installed Data Warning

Moodle may show:

Composer installed data not found
Composer dependencies were not found.
Make sure the "composer install --no-dev --classmap-authoritative" command has been run.

If composer uses PHP 8.2 by default, you may get:

Root composer.json requires php >=8.3.0 but your php version (8.2.30) does not satisfy that requirement.

Run Composer using PHP 8.3:

cd ~/public_html
COMPOSER_MEMORY_LIMIT=-1 /opt/cpanel/ea-php83/root/usr/bin/php $(which composer) install --no-dev --classmap-authoritative

If Composer path is unknown:

which composer
type composer
find /usr/local/bin /usr/bin /opt/cpanel -name composer -o -name composer.phar 2>/dev/null

Then run:

cd ~/public_html
COMPOSER_MEMORY_LIMIT=-1 /opt/cpanel/ea-php83/root/usr/bin/php /path/to/composer install --no-dev --classmap-authoritative

After Composer finishes:

/opt/cpanel/ea-php83/root/usr/bin/php ~/public_html/admin/cli/purge_caches.php

If Composer fails due to hosting memory limits, run Composer on another server or local machine using the same Moodle 5.2 files, then upload the generated vendor/ directory to:

/home/devopsschoolxyz/public_html/vendor

13. Fix Router Not Configured Warning

If Moodle shows:

Router not configured

Make sure this exists:

cat ~/public_html/public/.htaccess

Expected:

DirectoryIndex index.php
FallbackResource /r.php

Make sure config has:

grep routerconfigured ~/config.php

Expected:

$CFG->routerconfigured = true;

If missing:

grep -q "routerconfigured" ~/config.php || sed -i "/CFG->libdir/a \$CFG->routerconfigured = true;" ~/config.php

Then purge caches:

/opt/cpanel/ea-php83/root/usr/bin/php ~/public_html/admin/cli/purge_caches.php

14. Enable Asynchronous Backups

Moodle may show:

Asynchronous backups disabled

Enable it:

/opt/cpanel/ea-php83/root/usr/bin/php ~/public_html/admin/cli/cfg.php --name=enableasyncbackup --set=1

Purge caches:

/opt/cpanel/ea-php83/root/usr/bin/php ~/public_html/admin/cli/purge_caches.php

Important: asynchronous backups depend on cron. If cron is broken, backups may queue but not process.


15. Run Moodle Environment Checks

Run:

/opt/cpanel/ea-php83/root/usr/bin/php ~/public_html/admin/cli/checks.php

Also check in browser:

Site administration → Server → Environment

Common issues and fixes:

CheckFix
max_input_vars too lowSet max_input_vars = 5000
Composer data missingRun Composer with PHP 8.3
Router not configuredAdd FallbackResource /r.php and $CFG->routerconfigured = true
Async backups disabledSet enableasyncbackup = 1
Cron not runningAdd cPanel cron job

16. Configure Cron

Moodle cron should run every minute.

In cPanel Cron Jobs, add:

* * * * * /opt/cpanel/ea-php83/root/usr/bin/php /home/devopsschoolxyz/public_html/admin/cli/cron.php >/dev/null 2>&1

Manual test:

cd ~/public_html
/opt/cpanel/ea-php83/root/usr/bin/php admin/cli/cron.php

If you see memory errors like:

mmap() failed: Cannot allocate memory

check limits:

free -m
ulimit -a
/opt/cpanel/ea-php83/root/usr/bin/php -i | grep -i "memory_limit\|opcache"

On shared hosting, CloudLinux/LVE memory limits can break cron even if the server itself has free RAM. In that case, increase account memory limits or move Moodle to a VPS.


17. Verify Site From Command Line

Test homepage:

curl -k -I 'https://www.devopsschool.xyz/'

Expected:

HTTP/2 200

Test login page:

curl -k -I 'https://www.devopsschool.xyz/login/index.php'

Expected:

HTTP/2 200

If you see 500, check logs.


18. Check Logs

For cPanel, there may be more than one error log.

Check common logs:

tail -n 80 ~/public_html/error_log
tail -n 80 /home/devopsschoolxyz/logs/devopsschool_xyz.php.error.log

Find recent logs:

find /home/devopsschoolxyz -type f \( -name "error_log" -o -name "*.log" \) -mmin -20 -printf "%TY-%Tm-%Td %TH:%TM %p\n" 2>/dev/null | sort

To clear logs before testing:

: > ~/public_html/error_log
: > /home/devopsschoolxyz/logs/devopsschool_xyz.php.error.log

Then hit the failing URL:

curl -k -I 'https://www.devopsschool.xyz/login/index.php'

Then read logs:

tail -n 80 ~/public_html/error_log /home/devopsschoolxyz/logs/devopsschool_xyz.php.error.log

19. Install a Moodle 5.2-Compatible Theme: Moove

Moove is a good theme for training LMS sites. It has a Moodle 5.2-compatible version, and it works well for learner-focused course portals.

Install Moove:

cd ~

echo "===== Backup current Moodle config + theme folder ====="
mkdir -p ~/moodle-theme-backups
tar -czf ~/moodle-theme-backups/before-moove-$(date +%F-%H%M%S).tar.gz \
  ~/config.php \
  ~/public_html/config.php \
  ~/public_html/theme \
  ~/public_html/.htaccess

echo "===== Download Moove theme ====="
rm -rf /tmp/moove.zip /tmp/moodle-theme_moove-main /tmp/moove-install
mkdir -p /tmp/moove-install

wget -O /tmp/moove.zip https://github.com/willianmano/moodle-theme_moove/archive/refs/heads/main.zip

echo "===== Extract Moove ====="
unzip -q /tmp/moove.zip -d /tmp/moove-install

echo "===== Install into Moodle theme directory ====="
rm -rf ~/public_html/theme/moove
mv /tmp/moove-install/moodle-theme_moove-main ~/public_html/theme/moove

echo "===== Permissions ====="
find ~/public_html/theme/moove -type d -exec chmod 755 {} \;
find ~/public_html/theme/moove -type f -exec chmod 644 {} \;

echo "===== Verify theme version ====="
grep "plugin->component\|plugin->version\|plugin->requires\|plugin->supported" ~/public_html/theme/moove/version.php

echo "===== Run Moodle upgrade ====="
/opt/cpanel/ea-php83/root/usr/bin/php ~/public_html/admin/cli/upgrade.php --non-interactive

echo "===== Set Moove as active theme ====="
/opt/cpanel/ea-php83/root/usr/bin/php ~/public_html/admin/cli/cfg.php --name=theme --set=moove

echo "===== Purge caches ====="
/opt/cpanel/ea-php83/root/usr/bin/php ~/public_html/admin/cli/purge_caches.php

echo "===== Test site ====="
curl -k -I 'https://www.devopsschool.xyz/'
curl -k -I 'https://www.devopsschool.xyz/login/index.php'

Expected:

HTTP/2 200
HTTP/2 200

If the theme does not appear in Moodle UI, force plugin scan:

cd ~/public_html

find theme/moove -type d -exec chmod 755 {} \;
find theme/moove -type f -exec chmod 644 {} \;

/opt/cpanel/ea-php83/root/usr/bin/php admin/cli/upgrade.php --non-interactive
/opt/cpanel/ea-php83/root/usr/bin/php admin/cli/purge_caches.php

Then check:

Site administration → Plugins → Plugins overview

Search for:

Moove

Then select it:

Site administration → Appearance → Theme selector

20. Final Verification Checklist

Run:

echo "===== Moodle version ====="
grep '$release' ~/public_html/version.php
grep '$branch' ~/public_html/version.php

echo "===== PHP version ====="
/opt/cpanel/ea-php83/root/usr/bin/php -v

echo "===== PHP modules ====="
/opt/cpanel/ea-php83/root/usr/bin/php -m | grep -Ei "mysqli|pdo_mysql|iconv|intl|mbstring|curl|zip|gd|soap|sodium|fileinfo|exif|xml|dom|simplexml"

echo "===== Moodle CLI checks ====="
/opt/cpanel/ea-php83/root/usr/bin/php ~/public_html/admin/cli/checks.php

echo "===== URLs ====="
curl -k -I 'https://www.devopsschool.xyz/'
curl -k -I 'https://www.devopsschool.xyz/login/index.php'

Good final result:

Moodle 5.2+
Branch 502
PHP 8.3.x
Homepage HTTP/2 200
Login HTTP/2 200

21. Common Errors and Fixes

Error: Moodle requires the iconv PHP extension

Check CLI:

/opt/cpanel/ea-php83/root/usr/bin/php -m | grep iconv

If missing, enable iconv for PHP 8.3 in cPanel.


Error: database driver problem detected

Usually missing MySQL extensions.

Check:

/opt/cpanel/ea-php83/root/usr/bin/php -m | grep -Ei "mysqli|mysqlnd|pdo_mysql"

Expected:

mysqli
mysqlnd
pdo_mysql

Error: Undefined property: stdClass::$libdir

Your real config is missing:

$CFG->libdir = $CFG->dirroot . '/lib';

Add:

grep -q "CFG->libdir" ~/config.php || sed -i "/CFG->dirroot/a \$CFG->libdir    = \$CFG->dirroot . '/lib';" ~/config.php

Error: root index throws rootdirpublic

This is expected in Moodle 5.2 if Apache serves the wrong directory.

Fix by either:

DocumentRoot → /home/user/public_html/public

or use .htaccess rewrite in public_html.


Error: Homepage works but login gives 500

Check:

tail -n 80 /home/devopsschoolxyz/logs/devopsschool_xyz.php.error.log ~/public_html/error_log

Common causes:

missing PHP extension
wrong config.php loader
missing $CFG->libdir
missing Composer vendor files
routing not configured
theme/plugin issue

Error: Composer installed data not found

Run Composer using PHP 8.3:

cd ~/public_html
COMPOSER_MEMORY_LIMIT=-1 /opt/cpanel/ea-php83/root/usr/bin/php $(which composer) install --no-dev --classmap-authoritative

22. Rollback Plan

If upgrade fails badly:

Restore files

cd ~
tar -xzf ~/moodle-backups/moodle-files-before-52-YYYY-MM-DD-HHMMSS.tar.gz -C /

Restore database

gunzip < ~/moodle-db-YYYYMMDD-HHMMSS.sql.gz | mysql -u DBUSER -p DBNAME

Example:

gunzip < ~/moodle-db-20260503-193000.sql.gz | mysql -u devopsschoolxyz_user -p devopsschoolxyz_lms

Then purge cache:

/opt/cpanel/ea-php83/root/usr/bin/php ~/public_html/admin/cli/purge_caches.php

23. Security Cleanup After Upgrade

If you pasted or exposed the DB password during troubleshooting, rotate it.

Steps:

cPanel → MySQL Databases → MySQL Users → Change Password

Then update:

/home/devopsschoolxyz/config.php

Change:

$CFG->dbpass = 'NEW_PASSWORD_HERE';

Then test:

/opt/cpanel/ea-php83/root/usr/bin/php ~/public_html/admin/cli/upgrade.php
curl -k -I 'https://www.devopsschool.xyz/'

24. Summary

You upgraded Moodle successfully when these conditions were true:

Moodle files: 5.2+
Branch: 502
PHP: 8.3
Required PHP extensions: installed
public/ routing: configured
router: configured
Composer dependencies: installed or vendor exists
homepage: HTTP 200
login page: HTTP 200

The biggest Moodle 5.2 lesson:

Do not flatten Moodle 5.2.
Do not copy public/ contents into root.
Do not edit Moodle core files.
Configure Apache/cPanel routing correctly.

For cPanel shared hosting, the practical final structure is:

/home/devopsschoolxyz/public_html          Moodle application root
/home/devopsschoolxyz/public_html/public   Moodle public webroot
/home/devopsschoolxyz/config.php           real Moodle config
/home/devopsschoolxyz/ds-moodledata        Moodle data directory

That gives you a clean Moodle 5.2 upgrade path without changing core Moodle code.