Monday, October 12, 2026

upgrading Moodle 4.5 on Ubuntu 24.04 to Moodle 5.3 on Ubuntu 26.04

Initial backup snapshot

https://allinonelms.com/blog/moodle-5-3

says we have one year time before moving from 4.5 to 5.3, so we can wait for Dec, Moodle 5.3.1, if plugins break. But we went ahead, and plugins broke. 

Our Ubuntu install also needed to be upgraded from 24.04 to 26.04, since MySQL 8.4 is required by Moodle 5.3.

The idea is to use sudo do-release-upgrade
(if no upgrade is available, it will tell us)
after taking a backup snapshot.

Duck.ai gave the detailed steps for 

  1. create a snapshot of the original OS disk,
  2. generate a new managed disk from that snapshot,
  3. then deploy a new virtual machine using the new managed disk. 
I did only 1. and 2., did not need to do 3. It just took a few seconds to create snapshot and generate a new managed disk from the snapshot, for our 45 GB OS disk. Just to be safe, I stopped the services, then stopped the VM, then took the snapshot.

sudo service apache2 stop
sudo service postgresql stop
sudo service mysql stop


Gemini, on prompting, listed the disadvantages of upgrading vs creating a new VM from scratch, like the need to choose old vs new config files, the need to have downtime, etc. (private link to conversation is here for my reference, https://aistudio.google.com/prompts/1X_RhD-CQTQS1DdVHShfBhK67VxMp6uYk )

Since the CMS PHP code would also be migrated to PHP 8.5 with this upgrade, tested for breaks using Antigravity - just fed in the code, and asked for what to patch. Only the deprecated FILTER_SANITIZE_STRING in login-action.php and a suggested composer update for dependencies

sudo -u www-data composer update --no-dev --optimize-autoloader

(If done with sudo, would need 
sudo chown -R www-data:www-data /var/www/html/vendor
sudo chown www-data:www-data /var/www/html/composer.lock
)

To have a temporary 'Site under Maintenance' message, a cloudflare redirect to a page hosted on github pages, Rules > Overview > Redirect rules - struggled a bit to find the location of the redirect rules page, private link to conversation is at https://aistudio.google.com/prompts/1iFwSbjXsmI1TsYEIIlQcxvDM0VfolK2I 


Errors during the do-release-upgrade

While doing the do-release-upgrade after saving the snapshot, these were the problems observed.

rclone warning - not from ubuntu repos.
will need to reinstall, maybe.

login.defs - chose to upgrade

Postgres said,
Success. Please check that the upgraded cluster works. If it does,
you can remove the old cluster with
    pg_dropcluster 16 main

certbot not upgraded, dependency problems.

Package apache2 is not configured yet. Will defer actions by package libapache2-mod-php8.5.
Processing triggers for dbus (1.16.2-2ubuntu4) ...
Errors were encountered while processing:
 apache2
 python3-certbot-apache
needrestart is being skipped since dpkg has failed
packages have been installed but needrestart is suspended

etc.

dpkg: error processing package python3-certbot-apache (--configure):
 dependency problems - leaving unconfigured
Errors were encountered while processing:
 apache2
 python3-certbot-apache

Upgrade complete

The upgrade has completed but there were errors during the upgrade
process.

To continue please press [ENTER]
=== Command detached from window (Sun Oct 11 04:49:30 2026) ===

Gemini, in this conversation, https://aistudio.google.com/prompts/1ySB2bg3r8w23ODeVyiyw-hyOXi-u8gmI said that we can safely reboot, the issue is with apache (and since apache is required for certbot, that fails too). Apache would be loading the old PHP versions, we have to disable PHP 8.3 and enable PHP 8.5. But

sudo a2dismod php8.3
ERROR: /etc/apache2/mods-enabled/php8.3.load is not a symbolic link, not deleting
ERROR: /etc/apache2/mods-enabled/php8.3.conf is not a symbolic link, not deleting
Module php8.3 disabled.

So somewhere the mods-enabled got turned into real files. Had to find all real files (and not links) with

sudo find /etc/apache2/*-enabled/ -type f

and delete all of them. 

sudo bash -c '
# Fix conf-enabled
for file in /etc/apache2/conf-enabled/*; do
    if [ -f "$file" ] && [ ! -L "$file" ]; then
        name=$(basename "$file")
        if [ -f "/etc/apache2/conf-available/$name" ]; then
            rm -f "$file"
        else
            mv "$file" "/etc/apache2/conf-available/$name"
        fi
        ln -s "../conf-available/$name" "$file"
    fi
done

# Fix mods-enabled
for file in /etc/apache2/mods-enabled/*; do
    if [ -f "$file" ] && [ ! -L "$file" ]; then
        name=$(basename "$file")
        if [ -f "/etc/apache2/mods-available/$name" ]; then
            rm -f "$file"
        else
            mv "$file" "/etc/apache2/mods-available/$name"
        fi
        ln -s "../mods-available/$name" "$file"
    fi
done
'

Now 
sudo apache2ctl configtest
was OK, and restarting apache followed by dpkg 

sudo systemctl restart apache2
sudo dpkg --configure -a

fixed certbot also. Then, since postgres 18 was running fine,

sudo pg_dropcluster 16 main

sudo apt purge "postgresql-16*"
sudo apt autoremove


Clear browser host cache for testing, git upgrade

Flush DNS and test - Edge browser on Linux, method suggested by ChatGPT 

https://chatgpt.com/c/6acb1b41-4d04-83e8-a31f-f6958c4a2bbb
has many more testing and dns tips.

edge://net-internals/#dns
and choose Clear host cache.
and choose Flush socket pools.

Then the actual Moodle upgrade using git,
https://chatgpt.com/c/6acb1c57-0e94-83e8-aa2a-31ae3e8f9ef0
git branch -r
to list the remote branches, and then, following 
or

git branch --track MOODLE_503_STABLE origin/MOODLE_503_STABLE
git checkout  MOODLE_503_STABLE

sudo chown -R azureuser:www-data .
sudo chmod -R 775 .

sudo -u www-data /usr/bin/php admin/cli/upgrade.php 

At this point, after the upgrade completed, we needed to install all the PHP modules needed by Moodle 5.3.


Post-install fixes

Took the help of chatgpt to generate the commands needed to install all the required PHP modules,
sudo apt update

sudo apt install \
  php8.5-cli \
  php8.5-common \
  php8.5-curl \
  php8.5-gd \
  php8.5-intl \
  php8.5-mbstring \
  php8.5-mysql \
  php8.5-xml \
  php8.5-zip

/usr/bin/php --ini # to find the ini file path
Loaded Configuration File: /etc/php/8.5/cli/php.ini


Edit both the cli as well as the apache php.ini files to bump
max_input_vars = 5000

upload_max_filesize = 100M
post_max_size = 110M
memory_limit = 256M
max_execution_time = 300
max_input_time = 300


Needed to change the sites' document root in the sites-available of apache to append /public to the existing DocumentRoot value. Copied the "missing from disk" plugins to the public directory by asking chatgpt to write the commands as below,

cd /var/www/theMoodlegit

for plugin in \
    mod/quiz/accessrule/proctoring \
    admin/tool/tcpdffonts \
    availability/condition/othercompleted \
    availability/condition/stash \
    course/format/onetopic \
    course/format/remuiformat \
    course/format/tiles
do
    destination="public/$plugin"

    if [ -d "$plugin" ] && [ ! -e "$destination" ]; then
        sudo mv "$plugin" "$destination"
        echo "Moved $plugin"
    else
        echo "SKIPPED $plugin — check source and destination"
    fi
done

Had to install composer and run it, since Moodle 5.1 onwards use composer for dependencies. Without doing this, some plugins were complaining of Mustache errors, file not found etc. https://claude.ai/chat/427274e2-d874-4b60-b744-2989d51b6b72

sudo apt install composer

cd /var/www/theMoodlegit
composer install --no-dev --classmap-authoritative # (for each instance.)

Router not configured message still shows up, we need to follow the guide to set it up, would be mandatory for future versions of Moodle.

Lots of warnings like

PHP Deprecated:  Function xml_parser_free() is deprecated since 8.5, as it has no effect since PHP 8.0 in /var/www/theMoodlegit/public/lib/classes/xml_parser.php on line 190

ChatGPT recommends ignoring it till the Moodle team removes it, since Moodle 5.3 officially does not support PHP 8.5.

The installation and fixes so far took from 9 am to 2 pm with an hour's lunch break. The next day, the following were also fixed.

Theme edits and unsupported plugins 

Fix for duplicate logo appearing, by adding these lines to the Raw SCSS of Moove theme,

/* Dark mode is disabled, so hide the duplicate dark logo */
nav.navbar .navbar-brand img.logo.dark { display: none !important; }

Site Administration > Appearance > Themes > (gear icon of Moove theme) > Advanced tab > Raw SCSS textbox

(This duplication was a side-effect of us removing the Connectime branding logo advertisement of the Moove theme in SCSS.) 

Later, users found problems with courses using formattype onetopic (which has not been updated to support Moodle 5.3) - https://claude.ai/chat/5c5a6dd0-e0b9-4bfb-a41f-4b8062fabef6 - changed to topics instead in the database to fix,
UPDATE mdl_course SET format = 'topics' WHERE format = 'onetopic';
(after first trying it for a single course and testing.)

Similar fix for some other courses which were using formattype tiles - in that case, only the edit mode was affected, so the teacher admins could themselves change the course formattype to Multitopic (which is topics in the db) in the Course Settings in the UI.


Thursday, October 08, 2026

interesting results with 60 Moodle users logged in

One of our Ubuntu servers hosting Moodle instances had been bumped from D2asv5 (8 GB RAM) to D32asv5 (128 GB RAM) anticipating 100+ users for a training event. Interestingly, even with 60+ users logged on simultaneously, RAM usage did not cross 8 GB. So, tried using D4asv5 (16 GB RAM) for this standalone server, and then tried even not bumping it up from D2asv5. But with D2 (2 cpu cores), the CPU was continuously pegged at 100%, with load average going up to 15. Probably better to bump up to D4. Logging was via https://github.com/hn-88/log-memory-cpu-usage as mentioned in the previous post, and emails being sent to me similar to this - https://hnsws.blogspot.com/2021/02/scripts-to-alert-admin-when-hard-disk.html

(for my reference, conversation with Gemini to find the number of logged in users - and what they were doing - is at https://aistudio.google.com/prompts/1rp43v9LvY7d-b6pk82fgF0dDVQPWmJh8 )

Suggested database queries were - 

SELECT COUNT(DISTINCT l.userid) AS unique_users_active
FROM mdl_logstore_standard_log l
WHERE l.timecreated BETWEEN [START_UNIX_TIMESTAMP] AND [END_UNIX_TIMESTAMP]; 

to find the number of logged in users, and to see what they were doing, 

SELECT 
    u.username, 
    u.firstname, 
    u.lastname, 
    FROM_UNIXTIME(l.timecreated) AS time_of_action,
    l.eventname, 
    l.component, 
    l.target
FROM mdl_logstore_standard_log l
JOIN mdl_user u ON u.id = l.userid
WHERE l.timecreated BETWEEN [START_UNIX_TIMESTAMP] AND [END_UNIX_TIMESTAMP]
ORDER BY l.timecreated ASC;

Edit: Digging deeper, the limited RAM use is probably due to the MaxRequestWorkers setting in Apache settings as per https://docs.moodle.org/503/en/Performance_recommendations#Apache_Performance

Wednesday, October 07, 2026

convert UTC to IST in Google Sheets

 Running this simple memory and cpu logger on one of our servers, the timezone on that machine is UTC, to view in IST, we just have to add 5 hours and 30 minutes, like

=A2 + TIME(5, 30, 0)

Thursday, October 01, 2026

increasing per user quota in Google WorkSpace

There was a request from a user to increase the Google WorkSpace storage quota from 15 GB to 100 GB for two accounts. 

I asked Gemini via aistudio.google.com how I can increase the quota of a single user, (without changing their Organizational Unit quota) and it showed me this method. Accordingly, have set 100 GB for the group called higher storage users - the two user accounts have been added as members. Later, I got a confirmation from them that the quota increase was successful.
Here is how the admin can do it:

  1. Create a Group: In the Google Admin console, go to Directory > Groups and create a new group (e.g., "Higher Storage Users"). Add the single user to this group[3].
  2. Navigate to Storage: Go to Menu > Storage[4].
  3. Manage Limits: Under the Storage settings section, click Manage[4].
  4. Select the Group: On the left-hand panel, search for and select the configuration group you just created[3][4].
  5. Set the New Quota: Turn on the User storage limit, enter the new, higher storage amount for that user, and click Save[2][3].
Note: Group limits override Organizational Unit (OU) baseline limits, making this the best way to handle one-off storage increases.[3]

And the references cited were:

Tuesday, September 22, 2026

email problem - SPF and DKIM records

One of our admins reported a problem with replying to mails - they use Zoho - and copy-pasted Claude's solution - 

1. Modify the SPF record to include the zoho domain also - 
- Current value: v=spf1 include:zeptomail.net ~all
- New value: v=spf1 include:zoho.com include:zeptomail.net ~all

2. Publish the DKIM key. In Zoho Mail Admin Console > Domains > ourdomain.in > Email Configuration > DKIM > add selector. Zoho shows you a long key, which is to be added as a DNS TXT record.

The first time we did it, the DKIM record was sent to me by WhatsApp, and perhaps there was some non-printing character or line-break in the middle of the key, because of which the validator tool at https://mxtoolbox.com/SuperTool.aspx complained of invalid syntax for the DKIM key. Then I was sent the record by email as requested, and this time, copy-pasting it into Cloudflare's dashboard worked fine and the validator passed it. Currently no DMARC records are added, we can add those later if need be. And the problem seems to be solved.

Wednesday, September 16, 2026

updating to MacOS 27

The M4 Mac mini asked to download 13+ GB and update MacOS to version 27, Golden Gate - upgrading from MacOS Tahoe 26.6. Since the office fiber internet was down, I hesitated to start the process using Airtel 5G - but since 5G is supposed to be unlimited on my plan, went ahead.

It took nearly 90 minutes to complete the whole update, probably because while downloading, the 5G network was dropping down to 4G and getting throttled due to exhausting today's quota. On 5G, the download was happening at 30-45 Mbps. 

Interestingly, even after the "Downloading" phase completed and the "Preparing" phase started, there was still plenty of downloading activity as seen in Activity Monitor app. "Preparing" took half an hour. Then 8 minutes on the Apple logo screen while restarting. 

traceroutes just for general interest

I had asked an LLM about the network backhaul used by our ISPs like Airtel, BSNL etc, and was told that while there was no publicly available information about the actual specifications of their backhaul, a traceroute might give more information. Some traceroutes to google.com and nebula.tv from BSNLindicated that they terminated at their Points of Presence - POPs in Chennai - nebula.tv via Cloudflare. With Airtel, google.com seems to go to a Mumbai POP - bom.

traceroute to google.com (142.250.183.174), 64 hops max, 40 byte packets
 1  10.219.86.239 (10.219.86.239)  3.068 ms  3.368 ms  3.003 ms
 2  * * *
 3  192.168.116.2 (192.168.116.2)  64.774 ms  35.220 ms  29.967 ms
 4  * * *
 5  * * *
 6  125.21.99.61 (125.21.99.61)  36.796 ms
    dsl-ncr-dynamic-013.32.23.125.airtelbroadband.in (125.23.32.13)  28.051 ms  44.851 ms
 7  182.79.177.69 (182.79.177.69)  40.855 ms
    116.119.68.247 (116.119.68.247)  47.689 ms
    116.119.161.183 (116.119.161.183)  40.998 ms
 8  * 142.250.169.206 (142.250.169.206)  58.361 ms *
 9  142.250.230.197 (142.250.230.197)  158.451 ms *
    64.233.174.17 (64.233.174.17)  289.370 ms
10  142.251.55.67 (142.251.55.67)  38.116 ms
    142.251.55.230 (142.251.55.230)  233.997 ms
    142.251.55.42 (142.251.55.42)  37.590 ms
11  142.251.55.65 (142.251.55.65)  38.276 ms
    142.251.55.67 (142.251.55.67)  41.114 ms
    172.253.71.132 (172.253.71.132)  37.117 ms
12  bom07s32-in-f14.1e100.net (142.250.183.174)  58.148 ms  35.764 ms
    142.250.62.67 (142.250.62.67)  42.145 ms

Then, a couple of traceroutes to a US server, from BSNL fiber to home network done from a Windows machine

tracert ourdomain.org

Tracing route to ourdomain [redacted]
over a maximum of 30 hops:

  1     1 ms    <1 ms    <1 ms  192.168.1.1
  2     2 ms     4 ms     2 ms  117.192.80.1
  3     4 ms     3 ms     3 ms  10.219.29.194
  4     *        *        *     Request timed out.
  5     *        *        *     Request timed out.
  6    13 ms    13 ms    13 ms  10.200.120.225
  7     *        *        *     Request timed out.
  8    15 ms    14 ms    15 ms  115.110.161.189.static.vsnl.net.in [115.110.161.189]
  9    37 ms    50 ms    37 ms  172.25.199.34
 10    37 ms    36 ms    38 ms  172.28.124.134
 11    39 ms    36 ms    75 ms  172.25.138.206
 12     *        *        *     Request timed out.
 13     *        *        *     Request timed out.
 14     *        *        *     Request timed out.
 15     *      278 ms     *     if-bundle-12-2.qcore2.pvu-paris.as6453.net [80.231.245.13]
 16   272 ms   271 ms     *     ae0.cr2.dfw7.us.zip.zayo.com [64.125.20.63]
 17   272 ms   272 ms   293 ms  ae1.cr2.chd4.us.zip.zayo.com [64.125.25.76]
 18   257 ms     *      353 ms  ae9.cr1.phx4.us.zip.zayo.com [64.125.27.186]
 19     *        *        *     Request timed out.
 20   274 ms   272 ms   273 ms  64.125.199.194.IDIA-409280-003-ZYO.zip.zayo.com [64.125.199.194]
 21   241 ms   246 ms   241 ms  core1-edge2-lag1.phx1.codero.com [216.55.160.6]
 22   254 ms   251 ms   251 ms  216-55-160-81.dedicated.codero.net [216.55.160.81]
(and the next hop was our server in Phoenix.)

So apparently BSNL routed this request via Tata Communications (vsnl), via Paris interconnect to Zayo, and there to Phoenix. Of course, routes would vary from time to time, too.

Trying from a Mac via Airtel 5G network,

traceroute ourdomain.org
traceroute to ourdomain.org (redacted), 64 hops max, 40 byte packets
 1  10.219.86.239 (10.219.86.239)  3.615 ms  2.808 ms  2.575 ms
 2  * * *
 3  192.168.116.2 (192.168.116.2)  28.903 ms  32.648 ms  70.657 ms
 4  * 192.168.116.49 (192.168.116.49)  42.654 ms  33.930 ms
 5  * * *
 6  dsl-ncr-dynamic-013.32.23.125.airtelbroadband.in (125.23.32.13)  49.859 ms
    125.21.99.61 (125.21.99.61)  60.570 ms
    dsl-ncr-dynamic-013.32.23.125.airtelbroadband.in (125.23.32.13)  27.138 ms
 7  116.119.167.20 (116.119.167.20)  249.939 ms
    116.119.167.119 (116.119.167.119)  339.057 ms
    116.119.167.20 (116.119.167.20)  251.139 ms
 8  * * *
 9  be3271.ccr41.lax01.atlas.cogentco.com (154.54.42.101)  348.294 ms  258.764 ms  332.531 ms
10  be2932.ccr32.phx01.atlas.cogentco.com (154.54.45.161)  284.207 ms
    be2931.ccr31.phx01.atlas.cogentco.com (154.54.44.85)  256.272 ms  418.788 ms
11  be3452.rcr21.phx02.atlas.cogentco.com (66.28.4.242)  308.459 ms
    be3451.rcr21.phx02.atlas.cogentco.com (66.28.4.26)  432.845 ms  248.367 ms
12  38.122.88.107 (38.122.88.107)  253.042 ms  252.078 ms  246.671 ms
13  core1-edge1-lag1.phx1.codero.com (216.55.160.4)  273.957 ms  253.373 ms  259.721 ms
14  59-160-55-216.dedicated.codero.net (216.55.160.59)  303.754 ms  307.035 ms
    216-55-160-71.dedicated.codero.net (216.55.160.71)  259.048 ms
15  ourdomain.org (redacted)  444.128 ms  405.611 ms  409.528 ms

In this case, the route seemed to be within Airtel (Bharti Telecom) till the 8th hop, and then directly LA and then Phoenix.

 

Tuesday, September 15, 2026

unable to update plugins after a Moodle update

After upgrading our 4 Moodle instances to the latest minor version, 

Site Administration > Plugins > Plugins overview > Check for available updates button returns "Unable to fetch available updates data - unexpected HTTP response code."

Probably the cause is Moodle's Cloudflare ray rejecting the request from our server with a 403 (I verified that it was sending a 403 with curl) as mentioned in this thread, https://moodle.org/mod/forum/discuss.php?d=429373

So I'll try again later.