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 sudo privileges.
  • A domain or subdomain (this guide uses your_domain) with a DNS A record pointing to your_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 your location ~ [^/]\.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>) with sudo, then run sudo -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/moodle and 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.