Moodle 5.2.4 on AWS User Guide
Overview
This guide covers launching and using Moodle 5.2.4 on Ubuntu 24.04 from cloudimg on Amazon EC2. It is one version of the cloudimg Hardened Moodle & E-Learning Platforms AMIs listing on AWS Marketplace; you pick it as the software version when you subscribe and launch. The other learning platforms in the listing are described on the listing's guide hub.
Moodle is the world's most widely used open source learning management system (LMS): courses, activities (assignments, quizzes, forums, workshops, H5P content and more), enrolment, gradebooks, completion tracking, badges and reports, used by schools, universities and companies. Moodle is free software distributed under the GNU General Public License v3 or later. Moodle is a registered trademark of Moodle Pty Ltd; cloudimg is not affiliated with, endorsed by, or sponsored by Moodle Pty Ltd. This image packages the official Moodle 5.2.4 release together with cloudimg's provisioning, hardening and support.
What is included:
- Moodle 5.2.4 (Build: 20261005) in
/var/www/moodle, document root/var/www/moodle/public, data in/var/www/moodledata - nginx and PHP 8.3 (PHP-FPM, OPcache) at
https://<instance-address>/; port 80 only redirects to HTTPS - PostgreSQL 16 on the same instance, reachable only through its local Unix socket (no TCP listener) and with no database password
- Moodle's scheduled tasks (
admin/cli/cron.php) every minute from a systemd timer - A per-instance administrator password and HTTPS certificate, and the whole Moodle site, created on first boot. No default, shared or published credential ships in the image
- 24/7 cloudimg support
Security posture
- No default login, no shipped site. The image contains Moodle's code and an empty database server. On first boot
moodle-firstboot.servicecreates the database and installs the site with Moodle's own command-line installer, using an administrator password generated for this instance. Every per-site value Moodle creates (site identifier, keys, password hashes) is therefore unique to your instance. nginx and PHP-FPM stay stopped until first boot has finished, and the web installer (/install.php) is refused at all times. - No database password. Moodle connects to PostgreSQL as the operating-system user
www-dataover the local socket with PostgreSQL peer authentication. No PostgreSQL role has a password, and PostgreSQL does not listen on any TCP port. - Internet-open features are off. Email-based self-registration, the guest login button, web services and the Moodle mobile app endpoint are off on a new site. Turn on only what you need (Step 9).
- Server-side requests cannot reach the instance metadata service. Moodle's own block list for server-side fetches (Site administration > General > Security > HTTP security) includes the private ranges,
169.254.169.254and169.254.0.0/16, and nginx, PHP-FPM, PostgreSQL, Moodle cron and the first-boot units are additionally denied the EC2 metadata endpoint by systemd. - Updated bundled libraries. Moodle 5.2.4 bundles Guzzle 7.10.0, PSR-7 2.8.0, JMESPath 2.8.0 and FPDI 2.6.4, which have published advisories (including a Guzzle host-check bypass). This image replaces them, following Moodle's own update procedure, with the fixed releases Guzzle 7.16.1, PSR-7 2.13.2, Guzzle Promises 2.6.0, JMESPath 2.9.2 and FPDI 2.6.9. TinyMCE and PrismJS carry Moodle's own security backports (MSA-26-0045).
- HTTPS from first boot, address from configuration. nginx serves Moodle on port 443 with a per-instance self-signed certificate; port 80 returns a 301 redirect to HTTPS. Moodle's site address (
$CFG->wwwroot) is set from configuration (your DNS name, or the EC2 public name), never from a browser'sHostheader, and requests for any other host name are dropped. - Client IP from the socket. Moodle's login lockout and logs use the real connecting address: nginx drops client-supplied
X-Forwarded-For,X-Real-IP,Client-IPandForwardedheaders and Moodle is configured to trust onlyREMOTE_ADDR. - Uploads stay out of the web root. Uploaded files live in
/var/www/moodledata, outside the document root, and are only served through Moodle's permission checks. The code tree is read-only to the web server. - Hardened base. Key-only SSH with root login and X11 forwarding off, fully patched Ubuntu 24.04 at build time, and the PHP 8.3 build that fixes CVE-2026-109618 and CVE-2026-109619.
Known residual advisories (not reachable in Moodle): Moodle 5.2.4 also bundles PhpSpreadsheet 5.2.0, OTPHP 11.3.0, the AWS SDK for PHP 3.369.22, Slim 4.15.1 and php-jwt 6.11.1, which have published advisories in code paths Moodle does not use: Moodle only writes spreadsheets (every PhpSpreadsheet advisory is in a reader or the HTML writer), only generates TOTP secrets (the OTPHP advisories are in parsing provisioning URIs), never uses the AWS CloudFront signer, renders Slim errors with its own renderer, and the php-jwt finding is rated low. They are fixed when Moodle updates them upstream.
Step 1: Launch from AWS Marketplace
Subscribe to the Hardened Moodle & E-Learning Platforms AMIs listing, choose Moodle 5.2.4 on Ubuntu 24.04 as the software version, and launch. Recommended instance type: m5.large (2 vCPU, 8 GiB) for a pilot or a few hundred users; choose a larger instance for heavier use. In the security group open TCP 22 (SSH, your own address only), TCP 80 and TCP 443. Do not open 5432; PostgreSQL has no network listener.
The root volume is 12 GiB. Course files grow in /var/www/moodledata, so give the instance a bigger root volume at launch if you expect many uploads.
Step 2: Launch from the AWS CLI
aws ec2 run-instances \
--image-id <ami-id-from-the-listing> \
--instance-type m5.large \
--key-name <your-key> \
--security-group-ids sg-xxxxxxxx \
--subnet-id subnet-xxxxxxxx \
--associate-public-ip-address \
--block-device-mappings 'DeviceName=/dev/sda1,Ebs={VolumeSize=40,VolumeType=gp3}' \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=moodle}]'
First boot creates the database, installs the site and provisions the per-instance credentials. Allow about three minutes before the site answers.
Step 3: Connect over SSH
| OS variant | SSH login user |
|---|---|
| Ubuntu 24.04 | ubuntu |
ssh -i /path/to/your-key.pem ubuntu@<instance-address>
The login banner shows the site address and where the credentials are.
Step 4: Retrieve your credentials
The per-instance administrator password is written to a root-only file on first boot:
sudo cat /root/moodle-credentials.txt
The file gives the site address, the administrator user (admin) and its per-instance password (shown here as <redacted>):
moodle.url=https://ec2-100-59-14-14.compute-1.amazonaws.com/
moodle.admin.user=admin
moodle.admin.pass=<redacted>
There is no database password: Moodle reaches PostgreSQL as the system user www-data through the local socket.
Step 5: Sign in
Browse to the moodle.url address. The certificate is self-signed for this instance, so your browser shows a warning the first time; accept it, or install your own certificate (Step 7). Sign in as admin with the password from the credentials file.

After signing in you land on the dashboard:

Change the administrator password straight away under your user menu > Preferences > Change password, and set the administrator's real email address under Edit profile.
Step 6: Check the services
sudo moodle-set-hostname --show
systemctl is-active nginx php8.3-fpm postgresql@16-main moodle-cron.timer
curl -sk -o /dev/null -w '%{http_code}\n' https://127.0.0.1/login/index.php
sudo -u www-data php /var/www/moodle/admin/cli/cfg.php --name=release
Expected output on a healthy instance:
https://ec2-100-59-14-14.compute-1.amazonaws.com/
(automatic: EC2 instance address)
active
active
active
active
200
5.2.4 (Build: 20261005)
Moodle cron runs every minute from moodle-cron.timer as www-data. Check it with:
systemctl list-timers moodle-cron.timer --no-pager
Step 7: Use your own domain name and certificate
Point a DNS record at the instance (an Elastic IP keeps the address stable), then pin Moodle to that name:
sudo moodle-set-hostname lms.example.com
This writes $CFG->wwwroot = 'https://lms.example.com' into /var/www/moodle/config.php, makes nginx answer that name, issues a new self-signed certificate for it and purges Moodle's caches. sudo moodle-set-hostname --auto returns to the EC2 address. Without a pinned name, a stop/start that changes the public address is followed automatically on the next boot.
To use a certificate from a public authority, replace /etc/ssl/moodle/moodle.crt (full chain) and /etc/ssl/moodle/moodle.key (mode 0600, owner root) and reload nginx. For Let's Encrypt, for example:
sudo apt-get install -y certbot
sudo certbot certonly --webroot -w /var/www/moodle/public -d lms.example.com
sudo ln -sf /etc/letsencrypt/live/lms.example.com/fullchain.pem /etc/ssl/moodle/moodle.crt
sudo ln -sf /etc/letsencrypt/live/lms.example.com/privkey.pem /etc/ssl/moodle/moodle.key
sudo systemctl reload nginx
Step 8: Create your first course
Go to Site administration > Courses > Manage courses and categories > Create new course, give it a full and a short name, save, and add activities with Edit mode switched on.

Everything else (users, authentication, plugins, appearance, the site name and the front page) is under Site administration:

Set the outgoing mail server under Site administration > Server > Email > Outgoing mail configuration, so that notifications and password-reset emails are delivered (Amazon SES works well).
Step 9: Turn on the features you need
These are off on a new site, deliberately:
- Self-registration: Site administration > Plugins > Authentication > Manage authentication > Self registration: choose Email-based self-registration (needs working outgoing email).
- Guest access: Site administration > Plugins > Authentication > Manage authentication > Guest login button: Show.
- The Moodle mobile app: Site administration > General > Mobile app > Mobile settings > Enable web services for mobile devices.
- Web services for integrations: Site administration > General > Advanced features > Enable web services, then follow Site administration > Server > Web services > Overview.
Step 10: Back up and restore
Moodle's state is the PostgreSQL database plus /var/www/moodledata (and config.php). A consistent backup:
sudo install -d -m 0700 /root/moodle-backup
sudo -u postgres pg_dump --format=custom moodle | sudo tee /root/moodle-backup/moodle-db.dump >/dev/null
sudo tar -C /var/www -czf /root/moodle-backup/moodledata.tar.gz moodledata
sudo cp /var/www/moodle/config.php /root/moodle-backup/
sudo ls -lh /root/moodle-backup/
Copy the backup off the instance (for example to Amazon S3), or use EBS snapshots of the volume. To restore onto a fresh instance of this image, put the site in maintenance mode, then load the dump and the data directory:
sudo -u www-data php /var/www/moodle/admin/cli/maintenance.php --enable
sudo systemctl stop moodle-cron.timer php8.3-fpm
sudo -u postgres dropdb moodle
sudo -u postgres createdb --owner=www-data --encoding=UTF8 --template=template0 moodle
sudo cat /path/to/moodle-db.dump | sudo -u postgres pg_restore --no-owner --role=www-data -d moodle
sudo tar -C /var/www -xzf /path/to/moodledata.tar.gz
sudo systemctl start php8.3-fpm moodle-cron.timer
sudo -u www-data php /var/www/moodle/admin/cli/maintenance.php --disable
Moodle course backups (Course administration > Backup) are also available for individual courses.
Step 11: Updates
Ubuntu security updates install automatically (unattended-upgrades). Moodle itself is updated by replacing the code with a newer release and running sudo -u www-data php /var/www/moodle/admin/cli/upgrade.php; take a backup first and follow Moodle's upgrade notes for the target release. To reset a forgotten administrator password from the shell, run sudo -u www-data php /var/www/moodle/admin/cli/reset_password.php and answer its prompts (never put a password on a command line).
Troubleshooting
- The site does not answer after launch: first boot takes about three minutes. Check
sudo journalctl -u moodle-firstboot.service --no-pager. - "Incorrect access detected" or redirects to the wrong address: the site address is pinned to one name. Use
sudo moodle-set-hostname --show, thensudo moodle-set-hostname <your-name>or--auto. - Cron warnings on the Notifications page: check
systemctl list-timers moodle-cron.timerandsudo journalctl -u moodle-cron.service --no-pager | tail. - Files and logs: nginx
/var/log/nginx/, PHP-FPMjournalctl -u php8.3-fpm, PostgreSQL/var/log/postgresql/.
Support
cloudimg provides 24/7 technical support for this image by email and live chat: support@cloudimg.co.uk. Every product in this listing: Hardened Moodle & E-Learning Platforms AMIs guide hub.