Moodle is an open source learning management system used by schools, universities and companies to deliver courses, quizzes and assignments online. In this tutorial you will install Moodle 4.5 LTS on Ubuntu 24.04 with Nginx, PHP-FPM 8.3 and MariaDB, run the installer from the command line, secure the site with Let's Encrypt, and configure the scheduled task runner, Redis-backed sessions and nightly backups.
Prerequisites
To follow this guide you need:
- A server running Ubuntu 24.04 LTS, for example a CubePath VPS, with at least 2 vCPUs and 4 GB of RAM. Plan disk space for course files: 20 GB is a reasonable start.
- A non-root user with
sudoprivileges. - A domain or subdomain (this guide uses
your_domain) with a DNSArecord pointing toyour_server_ip. - Ports 80 and 443 open to the internet.
Step 1 - Installing Nginx, PHP and MariaDB
Moodle 4.5 supports PHP 8.1 to 8.3 and MariaDB 10.6.7 or newer, so the versions in Ubuntu 24.04 (PHP 8.3 and MariaDB 10.11) work directly. Install Nginx, PHP-FPM with the extensions Moodle requires, and MariaDB:
sudo apt update
sudo apt install nginx mariadb-server php-fpm php-cli php-mysql php-xml php-mbstring php-curl php-zip php-gd php-intl php-soap php-opcache
Confirm the PHP version and that the FPM service is running:
php -v
systemctl is-active php8.3-fpm
PHP 8.3.6 (cli) (built: ...) (NTS)
...
active
Open the firewall for SSH and web traffic:
sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw enable
Step 2 - Tuning PHP for Moodle
Moodle refuses to install unless max_input_vars is at least 5000, and the default upload limit of 2 MB is too small for course materials. Put the overrides in a separate file for PHP-FPM, which serves the site:
sudo nano /etc/php/8.3/fpm/conf.d/99-moodle.ini
max_input_vars = 5000
memory_limit = 256M
upload_max_filesize = 100M
post_max_size = 100M
The command-line PHP, used by the installer and the cron job, also checks max_input_vars:
echo 'max_input_vars = 5000' | sudo tee /etc/php/8.3/cli/conf.d/99-moodle.ini
Restart PHP-FPM and verify the new value:
sudo systemctl restart php8.3-fpm
php -i | grep max_input_vars
max_input_vars => 5000 => 5000
Step 3 - Creating the database
MariaDB on Ubuntu authenticates root through the Unix socket, so you can use sudo instead of a password. Run the hardening script first (answer n to switching to unix_socket authentication, since it is already in use, and Y to the rest):
sudo mariadb-secure-installation
Open the MariaDB shell:
sudo mariadb
Create the database with the full utf8mb4 character set and a dedicated user. Replace your_db_password with a strong password:
CREATE DATABASE moodle DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'moodleuser'@'localhost' IDENTIFIED BY 'your_db_password';
GRANT ALL PRIVILEGES ON moodle.* TO 'moodleuser'@'localhost';
FLUSH PRIVILEGES;
EXIT;
Check that the user can connect:
mariadb -u moodleuser -p -e 'SHOW DATABASES;'
+--------------------+
| Database |
+--------------------+
| information_schema |
| moodle |
+--------------------+
Step 4 - Downloading Moodle and creating the data directory
Download the latest 4.5 LTS package from the official download server and extract it into /var/www. The archive contains a single moodle directory:
cd /tmp
wget https://download.moodle.org/download.php/direct/stable405/moodle-latest-405.tgz
sudo tar --no-same-owner -xzf moodle-latest-405.tgz -C /var/www/
The code stays owned by root, so a compromised PHP process cannot modify it. Moodle keeps uploaded files, caches and sessions in a separate data directory, which must be writable by PHP-FPM (www-data) and must never be inside the web root:
sudo install -d -o www-data -g www-data -m 0770 /var/moodledata
ls -ld /var/www/moodle /var/moodledata
drwxr-xr-x ... root root 4096 ... /var/www/moodle
drwxrwx--- ... www-data www-data 4096 ... /var/moodledata
Step 5 - Configuring Nginx and HTTPS
Create a server block for Moodle:
sudo nano /etc/nginx/sites-available/moodle
server {
listen 80;
listen [::]:80;
server_name your_domain;
root /var/www/moodle;
index index.php;
client_max_body_size 100m;
location / {
try_files $uri $uri/ =404;
}
# Files that must not be served, as recommended by Moodle
location ~ (/vendor/|/node_modules/|composer\.json|/readme|/README|readme\.txt|/upgrade\.txt|/UPGRADING\.md|db/install\.xml|/fixtures/|/behat/|phpunit\.xml|\.lock|environment\.xml) {
return 404;
}
location ~ /\. {
return 404;
}
# Moodle uses "slash arguments" such as /pluginfile.php/12/...
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;
fastcgi_read_timeout 300;
}
}
The PHP location captures the path after the script name into PATH_INFO, which Moodle needs to serve course files and images. Enable the site, remove the default one and reload Nginx:
sudo ln -s /etc/nginx/sites-available/moodle /etc/nginx/sites-enabled/
sudo rm /etc/nginx/sites-enabled/default
sudo nginx -t
sudo systemctl reload nginx
Get a certificate before running the installer, because Moodle stores its public URL (wwwroot) and you want it to be https:// from the start:
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d your_domain
sudo certbot renew --dry-run
Step 6 - Running the Moodle installer
The command-line installer creates the database tables, the administrator account and config.php in one run. It needs to write config.php into the code directory, so give www-data temporary ownership of that directory only (not its contents):
sudo chown www-data /var/www/moodle
Run the installer as www-data. Replace the placeholders; the admin password must satisfy Moodle's default policy (at least 8 characters with a digit, a lowercase letter, an uppercase letter and a symbol):
cd /var/www/moodle
sudo -u www-data php admin/cli/install.php \
--lang=en \
--wwwroot=https://your_domain \
--dataroot=/var/moodledata \
--dbtype=mariadb --dbhost=localhost --dbname=moodle \
--dbuser=moodleuser --dbpass='your_db_password' \
--fullname="Your Site Name" --shortname="Moodle" \
--adminuser=admin --adminpass='your_admin_password' \
--adminemail=admin@your_domain \
--agree-license --non-interactive
The installer checks the environment, then installs each component. This takes several minutes:
...
-->System
++ Success ++
...
Installation completed successfully.
Return the directory to root and protect config.php, which contains the database password:
sudo chown root /var/www/moodle
sudo chown root:www-data /var/www/moodle/config.php
sudo chmod 640 /var/www/moodle/config.php
Open https://your_domain and log in as admin. You should see the Moodle dashboard.
Step 7 - Setting up the cron job
Moodle depends on a scheduled task runner for sending forum and notification emails, grading, course backups and cleanup. The recommended frequency is every minute. Create a cron file that runs it as www-data:
echo '* * * * * www-data /usr/bin/php /var/www/moodle/admin/cli/cron.php >/dev/null 2>&1' | sudo tee /etc/cron.d/moodle
Run it once by hand to make sure it works:
sudo -u www-data php /var/www/moodle/admin/cli/cron.php | tail -n 3
...
Cron run completed correctly
Cron completed at ...
In the web interface, Site administration > Notifications no longer shows the "cron has not been run" warning after a few minutes.
Step 8 - Storing sessions and caches in Redis
By default Moodle keeps sessions and caches as files in /var/moodledata. Redis is faster and avoids session locking problems once several people use the site at the same time. Install Redis and the PHP extension:
sudo apt install redis-server php-redis
sudo systemctl restart php8.3-fpm
redis-cli ping
PONG
Edit config.php:
sudo nano /var/www/moodle/config.php
Add the following lines above the line require_once(__DIR__ . '/lib/setup.php');:
$CFG->session_handler_class = '\core\session\redis';
$CFG->session_redis_host = '127.0.0.1';
$CFG->session_redis_port = 6379;
$CFG->session_redis_database = 0;
$CFG->session_redis_prefix = 'moodle_sess_';
$CFG->session_redis_acquire_lock_timeout = 120;
$CFG->session_redis_lock_expire = 7200;
Log out and back in, then confirm that sessions appear in Redis:
redis-cli --scan --pattern 'moodle_sess_*' | head -n 3
For the application cache, go to Site administration > Plugins > Caching > Configuration, add a Redis store instance with server 127.0.0.1 (use a different prefix from the sessions, for example mdl_cache_), and then use Edit mappings to set it as the store for Application caches.
Step 9 - Scheduling backups
A complete Moodle backup includes the database, config.php and the data directory. The cache, session and temporary directories inside moodledata can be skipped because Moodle rebuilds them. Create a backup script:
sudo nano /usr/local/sbin/moodle-backup
#!/usr/bin/env bash
set -euo pipefail
umask 077
BACKUP_DIR=/var/backups/moodle
STAMP=$(date +%F-%H%M)
mkdir -p "$BACKUP_DIR"
# Runs as root, so MariaDB authenticates through the Unix socket
mariadb-dump --single-transaction --default-character-set=utf8mb4 moodle \
| gzip > "$BACKUP_DIR/moodle-db-$STAMP.sql.gz"
tar -czf "$BACKUP_DIR/moodledata-$STAMP.tar.gz" -C /var \
--exclude=moodledata/cache --exclude=moodledata/localcache \
--exclude=moodledata/sessions --exclude=moodledata/temp \
moodledata
cp /var/www/moodle/config.php "$BACKUP_DIR/config.php-$STAMP"
find "$BACKUP_DIR" -type f -mtime +7 -delete
Make it executable, run it and check the files:
sudo chmod 700 /usr/local/sbin/moodle-backup
sudo /usr/local/sbin/moodle-backup
sudo ls -lh /var/backups/moodle
-rw------- 1 root root 1.5K Sep 25 11:40 config.php-2026-09-25-1140
-rw------- 1 root root 9.7M Sep 25 11:40 moodle-db-2026-09-25-1140.sql.gz
-rw------- 1 root root 64M Sep 25 11:40 moodledata-2026-09-25-1140.tar.gz
Schedule it for a quiet hour:
echo '30 3 * * * root /usr/local/sbin/moodle-backup' | sudo tee /etc/cron.d/moodle-backup
The moodledata archive grows with every course file, so keep an eye on disk usage and copy the backups to another server or to object storage.
Troubleshooting
- The installer stops with "max_input_vars must be at least 5000": the CLI override from Step 2 is missing. Check with
php -i | grep max_input_vars. - Images and course files return 404: the Nginx PHP location is not passing
PATH_INFO. Compare yourlocation ~ [^/]\.php(/|$)block with the one in Step 5. - Installing a plugin from the web interface says the directory is not writable: this is expected, because the code belongs to
root. Extract the plugin into its directory (for example/var/www/moodle/mod/<name>) withsudo, then runsudo -u www-data php /var/www/moodle/admin/cli/upgrade.php --non-interactive. - Emails are never sent: the cron job is not running. Check
/etc/cron.d/moodleand run the cron script by hand as shown in Step 7. Configure SMTP under Site administration > Server > Email > Outgoing mail configuration.
Conclusion
Moodle 4.5 LTS is now running on Ubuntu 24.04 behind Nginx with HTTPS, with its task runner scheduled every minute, sessions in Redis and nightly backups. Next, configure outgoing email, create course categories and your first course, and review Site administration > Reports > Security checks to fix any warnings before you invite users.
