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 SOP. 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 the customer 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 the customer in that case, but check with your lead engineer instead.

Check if server meets requirements

As stated in the moodle.org section on upgrading, make sure that the customer’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.)

  1. Install Ondřej Surý’s package for Apache 2:

      sudo add-apt-repository ppa:ondrej/apache2
      sudo apt-get update
  2. Then get the fastcgi mod for php:

      sudo apt install software-properties-common
      sudo apt install libapache2-mod-fcgid
  3. Add Ondřej’s PHP package:

      sudo add-apt-repository ppa:ondrej/php && sudo apt update
  4. Then install the required php version, e.g.:

      sudo apt-get install -y
      php7.3-{bcmath,bz2,intl,gd,mbstring,mysql,zip,common,fpm}
  5. 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/example-site/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 SOP.

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/example-site/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/example-site/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:

Plugin 3.1 3.4 Source Comments
Theme Lambda Yes Yes https://themeforest.net/item/lambda-responsive-moodle-theme/9442816 Only commercially available.Check with the design contact to see if they can download version 3.1 and 3.4. Otherwise ask if it’s alright to buy 3.4.
Grid course format Yes Yes https://moodle.org/plugins/format_grid
Local scorm log No No The current version might just work in 3.4. It looks pretty simple – just a bunch of DB functions in the lib.php file.
Mod Questionnaire Yes Yes https://moodle.org/plugins/mod_questionnaire
Block Progress Bar Yes No https://moodle.org/plugins/block_progress Block completion_progress should be used in 3.4. https://moodle.org/plugins/block_completion_progress
auth_mcae Not actually used
auth_emailadmin Not actually used

(Since the customer’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/the lead engineer/temp/moodle41-clean"
modified="/home/the lead engineer/temp/moodle41-modified"
target="/home/the lead engineer/php/icm/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-example-a.20240213/
multitenant-example-b.20240213/

Files multitenant-example-a.20240213/multitenant/lib.php and multitenant-example-b.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_STABLE branch 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 build
grep '\$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 the customer to approve

Put the test upgrade on a website under our domain. For instance, if the customer’s Moodle site is lms.example.com, create a domain lms.example.com.

But instead of creating a new Moodle website, upgrade the customer’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 the customer 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
During a major upgrade, Moodle does not tolerate the presence of any old files: leftovers from the previous version that are no longer in use. In fact, the upgrade process explicitly checks for the presence of quite a number of old files that should no longer be present in the code base. If it finds any old files, the upgrade process will halt with an error. That’s why we start the upgrade with a completely new branch and don’t branch off the master branch.

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:

  1. Check that the cli version of php is the same as the ‘web version’

  2. Create a Screen session (this means the upgrade will proceed even if your connection to the server is suddenly broken).

  3. Navigate to /admin/cli/ and perform the following command:

php upgrade.php (please use the correct php version here, e.g.
php5.6)

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 the customer

Once the test upgrade is done, contact the customer with the details of the test website, specifically the url.

Ask the customer 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:

  1. Put the Moodle website in maintenance mode: visit /admin/settings.php?section=maintenancemode to do that.

  2. Create a complete backup of:

    1. The moodledata directory

    2. The database

    3. The entire code base (can usually be found in public_html, htdocs, or a subdirectory called moodle)

  3. Alternatively, if the hosting environment supports it, you may instead create a snapshot of the entire system. (the hosting provider supports this through their KIS panel.)

  4. 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.

    1. 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)

    2. 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.

  5. Replace the source code with the new Moodle version by replacing the local master branch with the newly updated remote master branch:

git checkout master
git fetch origin
git reset --hard origin/master
  1. Copy the config.php file from the backup of the code base

  2. Go to the /admin/ url of the Moodle website to check if there are any issues with the plugins

  3. Go to the command line and start a Screen session

  4. Perform the actual upgrade through php admin/cli/upgrade.php

  5. Do some basic tests:

    1. Test if Moodle is still sending out email

    2. Make sure the cron job is still running properly (this can probably be done only after completing the next step)

  6. 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.

  7. Take the Moodle website out of maintenance mode on the /admin/settings.php?section=maintenancemode page.

Once the upgrade is done successfully, contact the customer.

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 admin@lms.example.com. 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:

  1. 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, optional healthcheck_url

  2. Add or update per-repo Git bot config:

    • Files: /opt/solin-deploy/config/gitbot-<repo>.config.json and /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, or main depending on the repo)

  3. Ensure repository token is loaded on coordinator host:

    • Runtime token file pattern: /run/solin-deploy/<repo>.token

    • Validate with: scripts/check-bitbucket-token.sh

  4. Configure Bitbucket webhook on the managed repo:
    
    
    
    URL: https://deploy-coordinator.example.internal/webhook/bitbucket
    
    
    Event: push
    
    
    Secret: must match SOLIN_WEBHOOK_SECRET in coordinator env
  5. Enable timer if not already enabled:

    • solin-deploy-gitbot-<repo>-nightly.timer

Editor’s note: This section is adapted from internal engineering notes and kept because it contains operational detail that may still be useful in the field.

Field Notes

Once a site is onboarded, the recurring minor upgrade is handled automatically by the coordinator + runner stack:

  1. Nightly Git bot fetches upstream Moodle tags and selects latest eligible patch tag (v4.5.*).

  2. Git bot merges that upstream tag into the configured site branch(es) and pushes.

  3. Bitbucket webhook creates a deploy job in the coordinator.

  4. Runner pulls the target commit, enables maintenance mode, runs admin/cli/upgrade.php –non-interactive, disables maintenance mode, and performs health checks.

  5. 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.

Solin plans and executes Moodle upgrades with staging, rollback strategy, plugin validation, and post-release checks. Need help? Contact us.

Contact us