Moodle is the world’s most widely used open-source learning management system (LMS): courses, quizzes, assignments, forums, grades, certificates and plugins for nearly everything, used by schools, universities and companies for training. Self-hosting gives you full control over student data and customisation.
Sizing
| Users (concurrent) | VPS |
|---|---|
| Up to ~50 | 2 vCPU, 4 GB RAM |
| 50–200 | 4 vCPU, 8 GB RAM |
| Larger, quiz-heavy peaks | 8+ vCPU, 16 GB+, separate DB, Redis, maybe several web nodes |
Quizzes taken by a whole class at the same moment create the heaviest load, so plan for peaks. Storage depends on course files and videos (consider external video hosting).
1. Install the stack
Check the PHP and database requirements for the Moodle version you’re installing (Moodle 4.5 LTS, for example, needs PHP 8.1–8.3).
sudo apt install nginx postgresql php-fpm php-pgsql php-curl php-gd php-intl php-mbstring php-xml php-zip php-soap php-redis git unzip
(For MariaDB, use mariadb-server and php-mysql.) See Nginx with PHP-FPM.
PHP settings (/etc/php/8.3/fpm/php.ini):
max_input_vars = 5000
memory_limit = 256M
upload_max_filesize = 200M
post_max_size = 200M
See php.ini settings.
2. Database
sudo -u postgres createuser --pwprompt moodleuser
sudo -u postgres createdb -O moodleuser -E UTF8 moodle
3. Code and moodledata
Get Moodle via Git (easy updates), with the branch for your version:
cd /var/www
sudo git clone -b MOODLE_405_STABLE --depth 1 https://github.com/moodle/moodle.git
sudo mkdir /var/moodledata
sudo chown -R www-data:www-data /var/moodledata
sudo chown -R root:www-data /var/www/moodle && sudo chmod -R 750 /var/www/moodle
moodledata must be outside the web root. It contains uploaded files and sessions, and must never be web-accessible.
4. Nginx
server {
server_name learn.example.com;
root /var/www/moodle;
index index.php;
client_max_body_size 200M;
location ~ [^/]\.php(/|$) {
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_index index.php;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
include fastcgi_params;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
location /dataroot/ {
internal;
alias /var/moodledata/; # for X-Accel-Redirect file serving
}
location ~ (/vendor/|/node_modules/|composer\.json|/readme|/README|readme\.txt|/upgrade\.txt|db/install\.xml|/fixtures/|/behat/|phpunit\.xml|\.lock|environment\.xml) {
deny all;
return 404;
}
}
Add HTTPS with Certbot. See Let’s Encrypt.
5. Run the installer
Open https://learn.example.com. The installer checks requirements, asks for the database details and the data directory (/var/moodledata), writes config.php (or shows it for you to create), and sets up the admin account and site settings. The CLI installer (admin/cli/install.php) works too.
Make config.php read-only for the web user afterwards.
6. Cron: required
Moodle depends on its cron (task scheduler) running every minute:
* * * * * www-data /usr/bin/php /var/www/moodle/admin/cli/cron.php > /dev/null 2>&1
Without it, notifications, forum emails, backups and many tasks don’t run. See crontab.
Performance
- OPcache enabled (essential).
- Redis for sessions and the Moodle Universal Cache (MUC): set it in Site administration → Plugins → Caching, and for sessions in
config.php. See install Redis. - Tune PHP-FPM workers for quiz peaks. See PHP-FPM tuning.
- Use X-Sendfile (
$CFG->xsendfile = 'X-Accel-Redirect'with the/dataroot/location) so Nginx serves files efficiently. - Database tuning. See PostgreSQL performance tuning.
Configure SMTP (Site administration → Server → Email → Outgoing mail configuration). Moodle sends many notifications. See transactional email services.
Backups
Three parts: the database, moodledata, and the code plus config.php:
sudo -u postgres pg_dump -Fc moodle > /backups/moodle-db-$(date +%F).dump
sudo tar -czf /backups/moodledata-$(date +%F).tar.gz -C /var moodledata
Enable maintenance mode during backups for full consistency on busy sites. Course backups inside Moodle are useful, but aren’t a substitute. See backup strategy.
Upgrades
sudo -u www-data php /var/www/moodle/admin/cli/maintenance.php --enable
cd /var/www/moodle && sudo git pull # minor updates in the same branch
sudo -u www-data php admin/cli/upgrade.php --non-interactive
sudo -u www-data php /var/www/moodle/admin/cli/maintenance.php --disable
For major versions, switch to the new stable branch (check PHP and DB requirements, and plugin compatibility), after a full backup and a test on staging. See updating self-hosted apps.
Quick reference
| Item | Value |
|---|---|
| Code | /var/www/moodle (Git branch MOODLE_XXX_STABLE) |
| Data | /var/moodledata (outside the web root) |
| Cron | admin/cli/cron.php every minute |
| Upgrade | admin/cli/upgrade.php |
| Maintenance mode | admin/cli/maintenance.php --enable |
Related: Redis vs Memcached and PHP versions.