Skip to content

Moodle

How to Install Moodle 5 on Ubuntu 24.04 (Nginx, PHP 8.3 and PostgreSQL)

Install Moodle 5.1 or 5.2 on Ubuntu 24.04: the new public/ directory, Composer dependencies, Nginx with the Moodle router, PostgreSQL, moodledata, cron and HTTPS.

7 min read By the Hanoo team

Moodle is the most widely used open-source learning platform, and Moodle 5 changed how it is installed. Since Moodle 5.1, the code is split into a web-facing public/ directory and private files above it, third-party libraries come from Composer, and the web server must be configured for Moodle's router. Most Moodle tutorials online predate all three changes.

This guide installs Moodle 5.1 or 5.2 on Ubuntu 24.04 LTS with Nginx, PHP 8.3 and PostgreSQL 16 — all from Ubuntu's own archive, no third-party repositories needed — and covers the parts that decide whether Moodle runs well: moodledata, file permissions, max_input_vars, cron and HTTPS.

Moodle 5 requirements

ComponentMoodle 5.1Moodle 5.2On Ubuntu 24.04
PHP8.2 – 8.48.3 or newerPHP 8.3 is the default — fits both
PostgreSQL14 or newer16 or newerPostgreSQL 16 is the default
MariaDB / MySQLMariaDB 10.11 / MySQL 8.4Check the release notesMariaDB 10.11 is the default
PHP max_input_vars5000 or more5000 or moreDefault is 1000 — must be raised
Web serverDocument root = public/Document root = public/Nginx or Apache

PHP 8.3 and PostgreSQL 16 from Ubuntu 24.04 satisfy both Moodle 5.1 and 5.2, which is why this guide uses them. Before upgrading or installing a newer branch, check Site administration → Server → Environment or the release notes on moodle.org.

PostgreSQL is Moodle's best-tested database and the one Moodle HQ recommends; MariaDB works too.

Step 1: install Nginx, PHP 8.3 and PostgreSQL

Terminal
sudo apt update && sudo apt upgrade -y
sudo apt install nginx postgresql git unzip composer \
  php8.3-fpm php8.3-cli php8.3-pgsql php8.3-curl php8.3-gd php8.3-intl \
  php8.3-mbstring php8.3-xml php8.3-zip php8.3-soap php8.3-opcache

Raise the PHP limits Moodle checks, in both /etc/php/8.3/fpm/php.ini and /etc/php/8.3/cli/php.ini (cron and the CLI tools use the second one):

INI
max_input_vars = 5000
memory_limit = 256M
upload_max_filesize = 100M
post_max_size = 100M
opcache.enable = 1
opcache.memory_consumption = 128
Terminal
sudo systemctl restart php8.3-fpm

Step 2: create the database

Terminal
sudo -u postgres psql
SQL
CREATE USER moodle WITH PASSWORD 'a-long-random-password';
CREATE DATABASE moodle WITH OWNER moodle ENCODING 'UTF8' TEMPLATE template0;

PostgreSQL on Ubuntu listens only on localhost by default, which is exactly what a single-server Moodle wants.

Step 3: download Moodle and install its dependencies

Use Git so later updates are a git pull rather than a re-download. Pick the newest stable branch listed on moodle.org — MOODLE_502_STABLE for Moodle 5.2, for example:

Terminal
sudo git clone --depth 1 --branch MOODLE_502_STABLE https://github.com/moodle/moodle.git /var/www/moodle
cd /var/www/moodle
sudo composer install --no-dev --classmap-authoritative

The Composer step is new in Moodle 5.1: a Git checkout no longer contains the vendor/ directory, and Moodle refuses to run without it.

Keep the code owned by root and not writable by the web server. Moodle never needs to write to its own code; if PHP cannot modify it, an attacker who finds a vulnerability cannot either:

Terminal
sudo chown -R root:root /var/www/moodle
sudo chmod -R a+rX /var/www/moodle

Step 4: create moodledata outside the web root

moodledata holds uploaded files, caches and sessions. It must never be reachable over HTTP:

Terminal
sudo mkdir /var/moodledata
sudo chown www-data:www-data /var/moodledata
sudo chmod 0770 /var/moodledata

Step 5: configure Nginx for the public directory and the router

Since Moodle 5.1, Nginx must serve /var/www/moodle/public — not the repository root — and send requests for paths that are not files to Moodle's router, r.php. Create /etc/nginx/sites-available/moodle:

Nginx
server {
    listen 80;
    server_name lms.example.com;

    root /var/www/moodle/public;
    index index.php;
    client_max_body_size 100M;

    location / {
        try_files $uri $uri/ /r.php$is_args$args;
    }

    location ~ [^/]\.php(/|$) {
        fastcgi_split_path_info ^(.+\.php)(/.*)$;
        if (!-f $document_root$fastcgi_script_name) {
            return 404;
        }
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
        fastcgi_pass unix:/run/php/php8.3-fpm.sock;
        fastcgi_read_timeout 300;
    }

    # Hidden files (.git, .env and friends) are never served.
    location ~ /\. {
        deny all;
    }
}

The PATH_INFO lines keep Moodle's "slash arguments" URLs (such as /pluginfile.php/12/…) working.

Terminal
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

Step 6: add HTTPS

Point lms.example.com at the server, then:

Terminal
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d lms.example.com

Do this before running the installer, so Moodle records an https:// address as its wwwroot from the start.

Step 7: run the installer

Open https://lms.example.com and follow the web installer:

  1. Data directory: /var/moodledata.
  2. Database driver: PostgreSQL (native/pgsql), host localhost, database moodle, user moodle.
  3. Because the code is not writable by PHP, the installer shows the contents of config.php instead of writing it. Save that text as /var/www/moodle/config.php — in the base directory, above public/, not inside it.
  4. Continue: Moodle checks the environment, installs its tables and asks you to create the first administrator.

Then enable the router in config.php, above the require_once line at the end of the file:

PHP
$CFG->routerconfigured = true;

The flag tells Moodle that the web server sends unknown paths to r.php. The Nginx rule and the flag must agree: with the rule but not the flag, routed pages return 404s.

Step 8: schedule Moodle's cron

Moodle's cron sends notifications, runs scheduled and ad-hoc tasks, and cleans up caches. Run it every minute as the web server user:

Terminal
sudo crontab -u www-data -e
crontab
* * * * * /usr/bin/php8.3 /var/www/moodle/public/admin/cli/cron.php > /dev/null

On Moodle 5.1 and later the CLI scripts live under public/admin/cli/; on Moodle 5.0 and earlier the path has no public/. Run ls /var/www/moodle/public/admin/cli/cron.php once to confirm it on your installation. Check Site administration → Notifications after a few minutes — Moodle warns there if cron has not run recently.

Step 9: production hardening

  • Caching: a Redis-compatible store (Redis or Valkey) for the application cache and sessions makes a large difference once more than a handful of people use the site. Configure it under Site administration → Plugins → Caching.
  • Backups: back up the PostgreSQL database and /var/moodledata together; one without the other cannot be restored.
  • Updates: cd /var/www/moodle && sudo git pull && sudo composer install --no-dev --classmap-authoritative, then complete the upgrade at Site administration → Notifications or with the CLI upgrade script.
  • Email: configure outgoing SMTP under Site administration → Server → Email, or users never receive password resets.

Running Moodle next to your other PHP apps

Moodle is not yet a one-click application in Hanoo — today's catalogue covers Laravel, WordPress, Magento, Drupal and plain PHP. If your team runs those alongside an LMS, Hanoo can manage them on servers you own with PHP 8.0–8.5 side by side, per-application users and PHP-FPM pools, MariaDB, domains with automatic HTTPS, cron jobs and Git deploys — while Moodle follows the manual setup above.

Frequently asked questions

Which PHP version should I use for Moodle 5?

PHP 8.3. It is supported by both Moodle 5.1 (PHP 8.2–8.4) and Moodle 5.2 (PHP 8.3 and newer), and it is the default PHP on Ubuntu 24.04, so no extra repository is needed.

What is the public directory in Moodle 5.1?

From Moodle 5.1, files that browsers may request live in public/, while config.php, vendor/ and other private files stay in the directory above it. The web server's document root must point at public/, which keeps configuration and dependencies out of reach of HTTP requests.

Why does Moodle show 404 errors after installing on Nginx?

Usually the router is not configured. Make sure Nginx's location / block ends with try_files $uri $uri/ /r.php$is_args$args;, that the PHP location passes PATH_INFO, and that config.php contains $CFG->routerconfigured = true;.

Should I use PostgreSQL or MariaDB for Moodle?

Both are supported. PostgreSQL is Moodle's most thoroughly tested database and is a good default for new installations; on Ubuntu 24.04, PostgreSQL 16 from the default archive meets Moodle 5's requirements.

Why must moodledata be outside the web root?

It contains every uploaded file, cached data and session. If it were inside the web root, those files could be downloaded directly, bypassing Moodle's permission checks entirely.

How often should Moodle cron run?

Every minute. Moodle decides internally which scheduled tasks are due, so a one-minute cron keeps notifications, forum emails and background tasks timely without overloading the server.