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:
| Check | Fix |
|---|---|
max_input_vars too low | Set max_input_vars = 5000 |
| Composer data missing | Run Composer with PHP 8.3 |
| Router not configured | Add FallbackResource /r.php and $CFG->routerconfigured = true |
| Async backups disabled | Set enableasyncbackup = 1 |
| Cron not running | Add 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.