# 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.

Source: https://hanoo.io/blog/install-moodle-5-ubuntu-24-04 · Published 2026-10-16 · Updated 2026-10-16 · 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.

> **In short:** install Nginx, PHP 8.3-FPM with Moodle's extensions and PostgreSQL; clone the Moodle stable branch and run `composer install --no-dev --classmap-authoritative`; create `moodledata` outside the web root; point Nginx's root at Moodle's `public/` directory with a fallback to `r.php`; finish the installer; set `$CFG->routerconfigured = true;`; and run Moodle's cron every minute.

## Moodle 5 requirements

| Component | Moodle 5.1 | Moodle 5.2 | On Ubuntu 24.04 |
| --- | --- | --- | --- |
| PHP | 8.2 – 8.4 | 8.3 or newer | PHP 8.3 is the default — fits both |
| PostgreSQL | 14 or newer | 16 or newer | PostgreSQL 16 is the default |
| MariaDB / MySQL | MariaDB 10.11 / MySQL 8.4 | Check the release notes | MariaDB 10.11 is the default |
| PHP `max_input_vars` | 5000 or more | 5000 or more | Default is 1000 — must be raised |
| Web server | Document 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

```bash
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
```

```bash
sudo systemctl restart php8.3-fpm
```

## Step 2: create the database

```bash
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:

```bash
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:

```bash
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:

```bash
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.

```bash
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:

```bash
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:

```bash
sudo crontab -u www-data -e
```

```cron
* * * * * /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.
