Moodle 5 with routing engine behind an nginx reverse proxy

April 2026

This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.

Moodle 5 vs. Moodle 4

For testing LTI related stuff I maintain a local Moodle install in a Podman container. From Moodle 4 to Moodle 5 two major changes in Moodle caused headaches during the update process. In fact it was a fresh install of Moodle 5, but the webserver configuration required a lot of tweaking to get it running. The two changes are:

The docs provide instructions on how to configure the webserver to get things running. But either I missinterpret something or the docs do not tell the full truth, at least for my setting (see below). Relevant doc pages are:

Setting

My setting is quite standard. A bare-metal Debian 13 machine hosts a Podman container with Moodle 5.2 inside. On the host an nginx webserver (reverse proxy) forwards requests starting with https://192.168.178.229/moodle to http://127.0.0.1:9001, where the Podman container’s nginx is listening and serving Moodle’s files. The reason for using the 192.168.. address is out of scope here and not related to the troubles during Moodle installation.

Inside the container Moodle’s files live in /var/www/html/moodle as suggested by Moodle’s install instructions. The webserver’s root directory is /var/www/html/moodle/public (this is a relevant change compared to Moodle 4).

The problem

If we follow Moodle’s install instructions, then Moodle cannot find its static files (CSS, JavaScript,…) and pages look very ugly and incomplete. The problem is, that the URL https://192.168.178.229/moodle/some.file maps to /var/www/html/moodle/public/moodle/some.file, which does not exist. The correct path would be /var/www/html/moodle/public/some.file. Up to Moodle 4 a somewhat ugly solution was to set the webserver’s root to /var/www/html. Then the /moodle in the URL corresponds nicely to the /moodle in /var/www/html/moodle. Due to the new public subdirectory this isn’t possible anymore.

A way out is to rewrite the URL. We can tell nginx to remove the /moodle part from the URL. Then static files are found by Moodle and everything is shiny and functional. But if we test the routing engine, for instance by opening https://192.168.178.229/moodle/course/2/manage (where 2 is a valid course ID), we get a Moodle styled 404 error. The router is not working!

Dropping the rewrite rule the router works like a charme. But static files are gone. Seems we have to choose: either static files or routing engine. This problem is not considered or even mentioned in the Moodle docs. What can we do now?

The problem’s root cause

Digging into Moodle’s routing engine’s code we find a method guess_basepath, which look suspicious in two regards:

The wwwroot intentionally contains the URL’s /moodle part. But the wwwroot value is by no means related to the file system paths on the server. Thus, I consider this a bug in Moodle’s code. Maybe I’m wrong. I’m neither a PHP guru nor a Moodle developer. But for the moment I do not have an alternative explanation for the guess_basepath code.

The work-around

For static files we have to drop the /moodle part of the URL. For routing engine requests we have to keep it. Thus, our nginx configuration inside the container has to distinguish both cases somehow. The good message is that this is simple (at least in principle): if the requested file (without /moodle) exists on the server, then use that file. If it doesn’t exist, forward the path to the routing engine and let it do its job. Troubles come with the details: The routing engine is a PHP script r.php. But all other requests to PHP scripts have to be handled like static files. Getting this sorted out requires a good understanding of how nginx processes requests, especially the execution order of location blocks. Have a look at

Here’s a working nginx site configuration (/etc/nginx/sites-available/some_site):

server {
        listen 80 default_server;
        listen [::]:80 default_server;

        server_name _;

        root /var/www/html/moodle/public;
        index index.php;

        absolute_redirect off;

        set $moodleurlpath '/moodle';
        rewrite /moodle/(.*) /$1 break;

        location / {
                try_files $uri $uri/ @rphp;
        }

        location @rphp {
                fastcgi_pass unix:/run/php/php-fpm.sock;
                include fastcgi_params;
                fastcgi_param SCRIPT_FILENAME $realpath_root/r.php;
                fastcgi_param DOCUMENT_ROOT $realpath_root;
                fastcgi_param PATH_INFO $moodleurlpath$uri;
                fastcgi_param DOCUMENT_URI $moodleurlpath$uri;
                fastcgi_param REQUEST_URI $moodleurlpath$uri$is_args$args;
                fastcgi_param SCRIPT_NAME $moodleurlpath$uri;
        }

        location ~ \.php(/|$) {
                fastcgi_split_path_info ^(.+\.php)(/.*)$;
                set $path_info $fastcgi_path_info;
                try_files $fastcgi_script_name $fastcgi_script_name/ /r.php$is_args$args;
                fastcgi_pass unix:/run/php/php-fpm.sock;
                include fastcgi_params;
                fastcgi_param PATH_INFO $path_info;
                fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
                fastcgi_param DOCUMENT_ROOT $realpath_root;
        }

        location ~ /\.(?!well-known).* {
            return 404;
        } 

        location ~ (/vendor/|/node_modules/|composer\.json|/readme|/README|readme\.txt|/upgrade\.txt|/UPGRADING\.md|db/install\.xml|/fixtures/|/behat/|phpunit\.xml|\.lock|environment\.xml) { 
            deny all;
            return 404; 
        }
        
}

The last two location blocks are security related stuff from the Moodle docs I don’t really care about. Processing now is as follows:

Asking the Moodle community

I already had a moodle.org account from reporting a minor bug several years ago. So I thought I could ask in the forums, what I’m doing wrong or whether the docs are wrong or whether that’s a bug in Moodle’s code. Sadly, my post was flagged as spam. Repeated attempts to get it published failed, my account was locked without providing any hint on why (‘your post looks like spam’). Two days after contacting the support I got my account back and the information that my post contained too many links!? So now there’s a forum post on this subject, split into two parts to circumvent spam detection, partly without proper formatting due to… I don’t know. A not so positive encounter with moodle.org.