Upgrading a Moodle Website
How to upgrade a Moodle installation using Git, covering the differences between minor and major upgrades, pre-upgrade checks, and post-upgrade validation.
Moodle upgrades involve sequencing: checking compatibility, updating code before running the database wizard, testing customizations, and validating the result. This guide covers the full workflow for both major and minor upgrades using Git.
Preamble: Minor vs. Major Upgrades
What is the difference between a minor upgrade and a major upgrade? A minor upgrade is usually a security patch which can be deployed by overwriting the core code base. By contrast, a major upgrade typically also requires you to upgrade parts of the LAMP stack Moodle is running on, as well as the theme (in case you have customized the theme for the client).
The following is mostly about performing a major upgrade. We will include a separate section on executing a minor upgrade at the end of the guide. If you just need to perform a minor upgrade, you can skip to that section right away.
Familiarize yourself with the Moodle upgrade procedure
Before you get started, check out the Upgrading page on Moodle’s website.
Upgrading in a nutshell
- Backup everything beforehand, specifically:
- The database
- The moodledata directory
- The entire code base (can usually be found in public_html, htdocs, or a subdirectory called moodle)
- Replace the code base with the new version
- Put the config.php file of the old version in the code base of the new version
- Add the new versions of the 3rd party plugins
Get access to Moodle and server
Ask your users for:
- Moodle url
- Moodle admin username
- Moodle admin password
- Web server url or ip address
- Web server username
- Web server password
If the Moodle website is hosted on one of our own servers, we should already have this information – don’t bother your users in that case, but ask the lead engineer instead.
Check if server meets requirements
As stated in the moodle.org section on upgrading, make sure that your users’s server meets the minimum requirements for the new Moodle version. The most important software to check is:
- MySQL (or whatever database they’re currently running Moodle on)
- In the case of upgrades from 2.9 to 3.1 or later, check whether the MySQL / MariaDB database can be converted from Antelope to Barracuda and the new character set & collation for.
- PHP – including the required extensions
If the server does not meet the requirements, make sure to check with the sysop whether the system can be updated. Do not perform the actual system updates yet, because that may disrupt the current Moodle version.
Installing a Separate PHP Version Using PHP-FPM under Ubuntu
If you are the sysop, or devop, you can also install the correct version of PHP yourself. (See also this site.)
- Install Ondřej Surý’s package for Apache 2:
- sudo add-apt-repository ppa:ondrej/apache2
- sudo apt-get update
- Then get the fastcgi mod for php:
- sudo apt install software-properties-common
- sudo apt install libapache2-mod-fcgid
- Add Ondřej’s PHP package:
- sudo add-apt-repository ppa:ondrej/php && sudo apt update
- Then install the required php version, e.g.:
- sudo apt-get install -y php7.3-{bcmath,bz2,intl,gd,mbstring,mysql,zip,common,fpm}
- Now add a directive to the vhost configuration file of the Moodle website, right before the ‘Directory’ part, e.g:
<FilesMatch \.php> # Apache 2.4.10+ can proxy to unix socket
SetHandler "proxy:unix:/var/run/php/php7.4-fpm.sock|fcgi://localhost/"
</FilesMatch>
<Directory /home/ocwdemo/public_html>
In some cases, it may be necessary to activivate the proxy_fcgi mod at this point:
sudo a2enmod proxy_fcgi proxy
- Also don’t forget to restart the webserver gracefully:
/etc/init.d/apache2 graceful
And if you're upgrading to a new php-fpm version, check the php.ini for that version, e.g.:
/etc/php/7.3/fpm/php.ini
And restart after changing any values (like max_post_size etc.) there:
/etc/init.d/php7.3-fpm restart
Please note: php-fpm also requires performance tweaking. This is outside the scope of this guide.
Upgrade Cron Job
If the php version has changed and you’re using multiple php versions on the same server, make sure that the command for the cron job execution is updated too:
E.g. if your php version was 7.0 for the previous version of Moodle, and your cron command is this:
/usr/bin/php7.0 /home/alpinevista/public_html/admin/cli/cron.php >/dev/null
Then you may need to change this to the latest php version:
/usr/bin/php7.4 /home/alpinevista/public_html/admin/cli/cron.php >/dev/null
Upgrade Backup Script
For most vhosts, we have a custom backup script running: Backupvhost Script. This script relies on the php version Moodle is using, so if the php version has changed for the Moodle upgrade, make sure to call the backup script with the new php version too. In addition, the location of the config.php may change from one version of Totara to another.
Take inventory of all ‘Additional’ (i.e. 3rd party) plugins
Visit /admin/plugins.php and note all ‘Additional’ plugins. These are 3rd party plugins for which you need to get the new source code separately, usually through https://moodle.org/plugins/.
Here’s an example:
(Since your users’s Moodle version was very old, in this example, we had to perform two subsequent upgrades: first to 3.1 and then to 3.4.)
Pay special attention to the theme
The theme is usually the only part that’s really been customized. In many cases, the theme will not survive any major upgrades, unless it’s a standard Moodle theme that has only had some configuration for an uploaded logo, some color customizations, and maybe a favicon added.
If the site looks very bad after the upgrade, it may be necessary to ask a Moodle theme developer to fix it. This must go through sales first (ask the lead engineer).
Look for any non-Moodle content which is stored in the Moodle directory
Anything that is not part of the Moodle code base (including plugins), such as external images, Flash files, movies, etc., should obviously not be stored in the Moodle directory (the wwwroot folder). But customers do this nonetheless. So be sure to copy that content and put it back in place after an upgrade.
Copy All Additional Plugins
For testing & development it may be convenient to simply copy over all ‘additional’ plugins from the old code base to the new. Here’s a script to do this:
#!/bin/bash
clean="/home/engineer/temp/moodle41-clean"
modified="/home/engineer/temp/moodle41-modified"
target="/home/engineer/php/example-site/public_html"
# Find directories in modified that are missing in clean, excluding hidden directories
find "$modified" -mindepth 1 -type d ! -path "*/.*" | while read dir; do
relative_path="${dir#$modified/}" # Get relative path
if [ ! -d "$clean/$relative_path" ]; then
echo "Copying: $relative_path"
rsync -av "$dir" "$target/${relative_path%/*}/"
fi
done
Check if there are any core hacks
It’s very unusual to find any core hacks on a customer’s Moodle website. Just ask them and if there’s any doubt, diff against the original code base for the exact same version, e.g.:
diff -qr moodle1/ moodle2
This compares two directories recursively (-r) and only reports the differences (-q). For example:
diff -qr multitenant-argononsand.20240213/ multitenant-v17test.20240213/
Files multitenant-argononsand.20240213/multitenant/lib.php and multitenant-v17test.20240213/multitenant/lib.php differ
Or, ignoring the .git directory:diff -qr --exclude='.git' moodle1/ moodle2
How to Find The Exact Moodle Build
If you want to compare the client’s Moodle code base with the ‘official’ code base, you need the exact same build. Here’s how to find it.
Switch to the stable branch and update it
In your local clone of the ‘official’ Moodle code base:
git checkout MOODLE_401_STABLE
git pull origin MOODLE_401_STABLE
- This ensures your local
MOODLE_401_STABLEbranch contains every version bump and bugfix up to the latest commit.
Search for the version bump in version.php
git log -S "20250131" -- version.php
The -S "20250131" flag looks for commits that add or remove the exact string 20250131 in version.php. The top result is the commit where Moodle’s maintainers changed:$release = '4.1.15+ (Build: 20250131)';
$version = 2022112815.06;
Check out that specific commit
Suppose the git log output shows a commit hash of 59a1f3e4a2b8c5d7e8f9a0123b4c5d6e7f8a9b0c. You can then do:git checkout 59a1f3e4a2b8c5d7e8f9a0123b4c5d6e7f8a9b0c
or, if you want a named branch at that point:
git checkout -b moodle-4.1.15-build20250131 59a1f3e4a2b8c5d7e8f9a0123b4c5d6e7f8a9b0c
Verify that version.php matches the desired buildgrep '\$version\|\$release' version.php
You should see exactly:$version = 2022112815.06;
$release = '4.1.15+ (Build: 20250131)';
At this point, your working copy is pinned to the precise Moodle build (4.1.15+ (Build: 20250131)) that matches the client’s code base.
Perform a test upgrade for your users to approve
Put the test upgrade on a website under our domain. For instance, if your users’s Moodle site is https://www.contoso-learning.example, create a domain contoso-learning.staging.example.
Then you need to create the actual virtual host for the test website. Essentially this comes down to Installing a New Moodle Website on a VPS.
But instead of creating a new Moodle website, upgrade your users’s.
Set Right Expectations for Test Version
In previous upgrade projects, we’ve encountered customers who mistakenly believed that the configuration changes they made to the test-version would be retained in the live-upgrade.
To prevent this, inform your users that any changes they make to the test-version will not be transferred to the live version of the upgrade.
Also, advise customers to test the customizations primarily. There is no real reason to test Moodle core or any of the much used 3rd party plugins since these will have been tested by the larger user community.
Get The Upgraded Code In The Repository
We deploy the upgrade using a Git branch, let’s call it upgrade. What’s more, the upgrade branch is going to be based on the upstream Moodle repository. In your local repo, do the following. Add the official Moodle repo as the upstream repo:
git remote add upstream https://github.com/moodle/moodle.git
Get a specific branch. Here, we’re pulling the branch containing Moodle 4.1 (the upstream branch name can be found in the upstream repo through git remote show upstream):
git fetch upstream MOODLE_401_STABLE
Create a local branch based on the upstream branch:
git checkout -b moodle41 upstream/MOODLE_401_STABLE
Optionally, if you want to store the 4.1 version in your remote repo:
git push -u origin moodle41
Create the ‘upgrade’ branch and check it out:
git checkout -b upgrade moodle41
Finish by adding, committing and pushing the new code:
git commit -a -m "Adding code for Moodle 4.1"
git push --set-upstream origin upgrade
Upgrading the website
Put the following items on the test website:
- The original database
- The original moodledata directory
- Checkout the git branch storing the source code for the upgrade (i.e. the new Moodle version)
- Add the new versions of the 3rd party plugins
- Copy the config-dist.php file from the source code to config.php and make the adjustments for the proper directory references and database access details.
- Check if there are any additional language packs that need upgrading
Go to the /admin/ url of the test website to trigger the actual upgrade process. Take a good look at the information on the ‘Current release information’ page that comes up just before you start the upgrade. Do not click on continue.
Are the Server Checks and Other Checks okay? (You can ignore any warnings about the site not being https if you’re on a test site).
Pay attention to collation issues: It is not enough to just change the collation in config.php! You should always run the following cli script if there are any collation issues:
php admin/cli/mysql_collation.php --collation=utf8mb4_unicode_ci
(Check Moodle documentation linked to in the /admin page.)
Once everything is ready for the actual upgrade, go to the command line interface. Then:
- Check that the cli version of php is the same as the ‘web version’
- Create a Screen session (this means the upgrade will proceed even if your connection to the server is suddenly broken).
- Navigate to /admin/cli/ and perform the following command:
php upgrade.php
Please use the correct PHP version here, e.g. php5.6 upgrade.php.
We don’t want to use the web interface for upgrading, because the upgrade can take a long time. You might run into timeout issues somewhere along the way.
Basic Tests
After the upgrade, perform a few basic tests:
- Test if Moodle is still sending out email
- Make sure the cron job is still running properly
Contact your users
Once the test upgrade is done, contact your users with the details of the test website, specifically the url.
Ask your users to approve the upgrade.
Also send the details of the test installation, such as the Moodle admin account (username and password) to our lead developer (the lead engineer, at the time of writing), including the MySQL password, the Unix password and the url. Be sure to do that in a secure way.
Perform the live upgrade
Prepare the Git repository for the deployment of the upgraded code. In your local repo, do the following:
git checkout upgrade
git pull
git checkout master
git pull
git reset --hard upgrade
git push --force origin master
- Re-add the new versions of all the missing plugins if necessary. (And then commit and push in Git).
- Then:
- Put the Moodle website in maintenance mode: visit /admin/settings.php?section=maintenancemode to do that.
- Create a complete backup of:
- The moodledata directory
- The database
- The entire code base (can usually be found in public_html, htdocs, or a subdirectory called moodle)
- Alternatively, if the hosting environment supports it, you may instead create a snapshot of the entire system. (Hosting Provider supports this through their KIS panel.)
- Update the system (i.e. the web server) if necessary – this may disrupt the current (old) Moodle installation, so make absolutely sure Moodle is in maintenance mode first.
- Also make sure there are no other websites that depend on the old version of php or the database (or whatever component you need to update on the system)
- Make sure that the settings of the current stack are retained. For example, if php.ini for php 7.1 has upload_max_filesize = 500M, then make sure that your new php 7.2 version has the same value in the new php.ini file. Idem ditto for post_max_size and max_execution_time.
- Replace the source code with the new Moodle version by replacing the local master branch with the newly updated remote master branch:
git checkout mastergit fetch origingit reset --hard origin/master- Copy the config.php file from the backup of the code base
- Go to the /admin/ url of the Moodle website to check if there are any issues with the plugins
- Go to the command line and start a Screen session
- Perform the actual upgrade through
php admin/cli/upgrade.php - Do some basic tests:
- Test if Moodle is still sending out email
- Make sure the cron job is still running properly (this can probably be done only after completing the next step)
- In some cases, it may be necessary to copy backend settings from the test server (e.g. if a theme was upgraded, make sure to copy any custom css). Use the Site admin presets tool for this:
/admin/tool/admin_presets/index.php. However, beware that this will take the site out of maintenance mode if you import from a site that has the setting disabled. - Take the Moodle website out of maintenance mode on the /admin/settings.php?section=maintenancemode page.
Once the upgrade is done successfully, contact your users.
Performing a Minor Upgrade
Minor upgrades are typically performed when Moodle HQ issues a security alert. The email has the subject “Moodle Security Alerts” and the sender is securityalerts@moodle.org. The upgrade will typically be a security patch.
In such a case, it is good practice to do a minor upgrade. For some customers, there is even a contractual obligation to perform minor upgrades.
Preparing The Git Repository
Manual minor-upgrade Git operations are no longer the standard process.
For websites that should receive automatic Moodle 4.5 minor upgrades, onboarding is now done by configuration on the automation hosts:
- Add the site to runner mapping:
- File:
/opt/solin-deploy/config/sites.json - Include:
site,git_root,webroot,deploy_user,php_binary,systemd_service_base,backup_log_path, optionalhealthcheck_url - Add or update per-repo Git bot config:
- Files:
/opt/solin-deploy/config/gitbot-<repo>.config.jsonand/opt/solin-deploy/config/gitbot-<repo>.env - Use
tag_glob: "v4.5.*" - Include the correct site repo path and branches (for example
live,staging,master, ormaindepending on the repo) - Ensure repository token is loaded on coordinator host:
- Runtime token file pattern:
/run/solin-deploy/<repo>.token - Validate with:
scripts/check-bitbucket-token.sh - Configure Bitbucket webhook on the managed repo:
- URL:
https://deploy.example.internal/webhook/bitbucket - Event: push
- Secret: must match
SOLIN_WEBHOOK_SECRETin coordinator env - Enable timer if not already enabled:
solin-deploy-gitbot-<repo>-nightly.timer
Steps To Do The Minor Upgrade
Once a site is onboarded, the recurring minor upgrade is handled automatically by the coordinator + runner stack:
- Nightly Git bot fetches upstream Moodle tags and selects latest eligible patch tag (
v4.5.*). - Git bot merges that upstream tag into the configured site branch(es) and pushes.
- Bitbucket webhook creates a deploy job in the coordinator.
- Runner pulls the target commit, enables maintenance mode, runs
admin/cli/upgrade.php --non-interactive, disables maintenance mode, and performs health checks. - On deployment failure, automatic rollback is executed with backup + optional Git commit restore, and an immediate rollback alert email is sent.
Why third-party plugins are not overwritten in minor upgrades
The automated minor upgrade uses a Git merge of upstream Moodle tag into the existing site branch. It does not replace the branch with a clean upstream tree.
As long as third-party plugins are committed in the site repository branch, they remain in place after the merge. In other words: plugin loss risk comes from branch history/content mistakes (for example, force-rewriting a branch without carrying custom plugin directories), not from the normal automated minor-upgrade merge process itself.
Troubleshooting
SSL / HTTPS Issue
During an upgrade I ran into the following issue: a test php script was running just fine, but whenever I tried to access Moodle itself, I was redirected to the server’s default site. In addition, https was enforced.
It turned out that the site had this setting:

This was a Moodle 3.1 site, from back when only the login pages had https.
This situation may arise when you run Moodle without an SSL certificate, for example because you’re using a temporary domain name and you want to add the certificate once you have attached the actual domain name to the same vhost.
If you find yourself in a similar situation, go to the script lib/setuplib.php and look for the initialise_fullme() function.
In this code block:
if (!empty($CFG->overridetossl {
if (strpos($CFG->wwwroot, 'http://') === 0) {
$CFG->wwwroot = str_replace('http:', 'https:', $CFG->wwwroot);
} else {
unset_config('overridetossl');
}
}
Copy unset_config('overridetossl'); to the top:
unset_config('overridetossl');
if (!empty($CFG->overridetossl {
if (strpos($CFG->wwwroot, 'http://') === 0) {
$CFG->wwwroot = str_replace('http:', 'https:', $CFG->wwwroot);
} else {
unset_config('overridetossl');
}
}
And now there won’t be a forced https redirect anymore.
H5p – execute task "Download available H5P content types from h5p.org" by setting it to every 1 minute (and then resetting to default settings).
Solin specializes in Moodle upgrades and version migrations.
Contact us