Every command, every fix, every edge case covered. Provision and deploy a completely secured Moodle 5.0 Stable environment on Apache, MariaDB, and PHP 8.1. Includes dedicated 100GB persistent volume storage configuration, SSL, Cron daemon, and email relays.
gd is used for user avatars, xml/mbstring for language localization, and intl for character conversion operations.
sudo apt update && sudo apt upgrade -y
sudo apt install apache2 mariadb-server mariadb-client -y
sudo apt install php8.1 php8.1-cli php8.1-common php8.1-mysql php8.1-xml php8.1-curl php8.1-gd php8.1-intl php8.1-mbstring php8.1-zip php8.1-soap php8.1-bcmath libapache2-mod-php8.1 -y
sudo apt install git nano cron sendmail certbot python3-certbot-apache unzip rsync -y
sudo a2enmod rewrite ssl headers
sudo systemctl restart apache2
libapache2-mod-php8.1 is missed, Apache will render raw PHP code as text instead of executing it. Always verify Apache handles PHP via php -m.
utf8mb4_unicode_ci, Moodle will fail during installations, throwing character set incompatibility exceptions.
CREATE DATABASE moodle_demo CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'moodleDemo2026'@'localhost' IDENTIFIED BY '2026PassDemo!!';
GRANT ALL PRIVILEGES ON moodle_demo.* TO 'moodleDemo2026'@'localhost';
FLUSH PRIVILEGES;
-- run mysql_secure_installation after
sudo nano /etc/php/8.1/apache2/php.ini
# Set mandatory parameters:
upload_max_filesize = 1080M
post_max_size = 1080M
max_execution_time = 3600
memory_limit = 512M
max_input_vars = 5000
max_input_time = 300
date.timezone = UTC
sudo systemctl restart apache2
cd /var/www
sudo git clone -b MOODLE_500_STABLE git://git.moodle.org/moodle.git moodl_ulo2026Demo
cd moodl_ulo2026Demo
sudo chown -R www-data:www-data /var/www/moodl_ulo2026Demo
sudo chmod -R 755 /var/www/moodl_ulo2026Demo
sudo fdisk /dev/sdb # n (new) → p (primary) → Enter → Enter → w
sudo mkfs.ext4 /dev/sdb1
sudo mkdir -p /var/moodledata
sudo mount /dev/sdb1 /var/moodledata
sudo blkid /dev/sdb1 # copy UUID
echo "UUID=your-uuid-here /var/moodledata ext4 defaults,noatime 0 2" | sudo tee -a /etc/fstab
sudo chown -R www-data:www-data /var/moodledata
sudo chmod -R 770 /var/moodledata
config.php maps database access keys. Note that dataroot directory MUST be placed outside your public web server folder to secure uploaded coursework.
cd /var/www/moodl_ulo2026Demo
sudo cp config-dist.php config.php
sudo nano config.php
# PASTE CONFIGURATION:
$CFG->dbtype = 'mariadb';
$CFG->dbhost = 'localhost';
$CFG->dbname = 'moodle_demo';
$CFG->dbuser = 'moodleDemo2026';
$CFG->dbpass = '2026PassDemo!!';
$CFG->prefix = 'mdl_';
$CFG->wwwroot = 'https://classroom.demodemo.com';
$CFG->dataroot = '/var/moodledata';
$CFG->directorypermissions = 02775;
$CFG->disablelogintoken = true;
$CFG->releasetype = 'stable';
sudo nano /etc/apache2/sites-available/moodle-ulo2026demo.conf
# Paste VirtualHost block below
sudo a2ensite moodle-ulo2026demo.conf
sudo a2dissite 000-default.conf
sudo systemctl reload apache2
<VirtualHost *:80>
ServerName classroom.demodemo.com
DocumentRoot /var/www/moodl_ulo2026Demo
<Directory /var/www/moodl_ulo2026Demo>
AllowOverride All
Require all granted
</Directory>
</VirtualHost>
sudo certbot --apache -d classroom.demodemo.com
sudo systemctl status certbot.timer
sudo certbot renew --dry-run
# Force HTTPS redirect enabled by certbot
sudo crontab -u www-data -e
# Add:
* * * * * /usr/bin/php /var/www/moodl_ulo2026Demo/admin/cli/cron.php > /dev/null
sudo systemctl enable cron
sudo -u www-data php /var/www/moodl_ulo2026Demo/admin/cli/cron.php
sendmailconfig configures default local delivery routes.
sudo apt install sendmail -y
sudo sendmailconfig # yes all defaults
# test: echo "Subject: test" | sendmail admin@demodemo.com
# optional but robust: ensure /var/moodledata is always mounted
sudo umount /var/moodledata || true
sudo mount -a
# verify: df -h | grep moodledata
# extra bind if needed: /mnt/moodledata /var/moodledata none bind 0 0
mysql -u root -p moodle_demo -e "DELETE FROM mdl_task_adhoc WHERE classname LIKE '%restore%';"
mysql -u root -p moodle_demo -e "DELETE FROM mdl_backup_controllers;"
sudo rm -rf /var/moodledata/temp/backup/* /var/moodledata/cache/* /var/moodledata/localcache/*
sudo chown -R www-data:www-data /var/moodledata /var/www/moodl_ulo2026Demo
sudo chmod -R 755 /var/www/moodl_ulo2026Demo
sudo chmod -R 770 /var/moodledata
sudo systemctl restart apache2 mariadb
sudo apache2ctl configtest
php /var/www/moodl_ulo2026Demo/admin/cli/check_database_schema.php
sudo -u www-data php /var/www/moodl_ulo2026Demo/admin/cli/cron.php --force
curl -I https://classroom.demodemo.com
$CFG->dbname='moodle_demo'; $CFG->dbuser='moodleDemo2026';
$CFG->dbpass='2026PassDemo!!'; $CFG->wwwroot='https://classroom.demodemo.com';
$CFG->dataroot='/var/moodledata'; $CFG->disablelogintoken=true;
<VirtualHost *:443>
ServerName classroom.demodemo.com
DocumentRoot /var/www/moodl_ulo2026Demo
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/classroom.demodemo.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/classroom.demodemo.com/privkey.pem
</VirtualHost>
* * * * * /usr/bin/php /var/www/moodl_ulo2026Demo/admin/cli/cron.php > /dev/null
PHP Opcache tuning:
opcache.enable=1; opcache.memory_consumption=256; opcache.max_accelerated_files=10000
MariaDB Optimization:
innodb_buffer_pool_size=1G
php admin/cli/cron.php --verbose
df -i /var/moodledata
sudo apt install redis php8.1-redis