Setting up Moodle on NGINX with Pretty URLs

How to Add Pretty URLs for Moodle Pages

For demonstration purposes, the sample domain https://SAMPLEMOODLE.COM has been used.

I'm working on Hostinger; other server providers probably work similarly.

Goal:

  • Original URL: https://SAMPLEMOODLE.COM/local/page/?id=2

  • Pretty URL: https://SAMPLEMOODLE.COM/prettyname

  • Dynamic: no need to hardcode every page ID.


Tutorial: Setting Up Pretty URLs for Moodle on NGINX

This guide explains how to configure NGINX so that your Moodle pages are accessible through clean, readable URLs such as:

https://SAMPLEDOMAIN.com/about
https://SAMPLEDOMAIN.com/contact
https://SAMPLEDOMAIN.com/licenses

instead of the default Moodle structure:

https://SAMPLEDOMAIN.com/local/page/?id=3
https://SAMPLEDOMAIN.com/local/page/?id=4
https://SAMPLEDOMAIN.com/local/page/?id=2

1. Open Your NGINX Configuration

Edit your NGINX server block for your site (usually located in /etc/nginx/sites-available/SAMPLEDOMAIN.com):

sudo nano /etc/nginx/sites-available/SAMPLEDOMAIN.com

2. Add Rewrites for Pretty URLs

Inside your main server { ... } block, locate or create a section for your Moodle site.

Add the following block:

# --- Pretty URLs for Moodle custom pages ---
location / {
    # Rewrite clean URLs to Moodle internal page IDs
    rewrite ^/licenses$ /local/page/?id=2 last;
    rewrite ^/about$    /local/page/?id=3 last;
    rewrite ^/contact$  /local/page/?id=4 last;
    rewrite ^/faq$      /local/page/?id=5 last;

    # Handle Moodle PHP files like /plugin/file.php/...
    rewrite ^/(.*\.php)(/)(.*)$ /$1?file=/$3 last;

    # Fallback to index.php for everything else
    try_files $uri $uri/ /index.php?$args;
}

✅ What this does

Each line tells NGINX to:

  • Match a friendly URL (e.g., /about)

  • Redirect internally to the correct Moodle page (e.g., /local/page/?id=3)

  • Keep the clean URL visible in the browser


3. How to Add a New Pretty URL

Let’s say you created a new Moodle page with ID 6 and want it to be available at https://SAMPLEDOMAIN.com/support.

Just add this line inside the same block:

rewrite ^/support$ /local/page/?id=6 last;

Now /support will display Moodle’s page ID 6.


4. How to Rename or Change a URL

If you want to rename /faq to /help, just replace:

rewrite ^/faq$ /local/page/?id=5 last;

with:

rewrite ^/help$ /local/page/?id=5 last;

5. Full Example Configuration

Here’s a complete working example, ready to use:

server {
  listen 80;
  listen [::]:80;
  listen 443 ssl http2;
  listen [::]:443 ssl http2;

  {{ssl_certificate_key}}
  {{ssl_certificate}}

  server_name SAMPLEDOMAIN.com;
  {{root}}

  {{nginx_access_log}}
  {{nginx_error_log}}

  # Allow access to ACME challenge for SSL certificates
  location ~ /.well-known {
    auth_basic off;
    allow all;
  }

  {{settings}}

  # --- Pretty URLs for Moodle custom pages ---
  location / {
    # Rewrite clean URLs to Moodle internal page IDs
    rewrite ^/licenses$ /local/page/?id=2 last;
    rewrite ^/about$    /local/page/?id=3 last;
    rewrite ^/contact$  /local/page/?id=4 last;
    rewrite ^/faq$      /local/page/?id=5 last;
    rewrite ^/support$  /local/page/?id=6 last;

    # Handle plugin PHP files like /plugin/file.php/...
    rewrite ^/(.*\.php)(/)(.*)$ /$1?file=/$3 last;

    # Fallback for all other requests
    try_files $uri $uri/ /index.php?$args;
  }

  # --- Moodle & PHP rewrites ---
  rewrite ^/(.*\.php)(/)(.*)$ /$1?file=/$3 last;

  try_files $uri $uri/ /index.php?$args;
  index index.php index.html;

  # --- PHP processing ---
  location ~ \.php$ {
    include fastcgi_params;
    fastcgi_intercept_errors on;
    fastcgi_index index.php;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    try_files $uri =404;
    fastcgi_read_timeout 3600;
    fastcgi_send_timeout 3600;
    fastcgi_param HTTPS $fastcgi_https;
    fastcgi_pass 127.0.0.1:{{php_fpm_port}};
    fastcgi_param PHP_VALUE "{{php_settings}}";
  }

  # --- Static files ---
  location ~* ^.+\.(css|js|jpg|jpeg|gif|png|ico|gz|svg|svgz|ttf|otf|woff|woff2|eot|mp4|ogg|ogv|webm|webp|zip|swf)$ {
    add_header Access-Control-Allow-Origin "*";
    expires max;
    access_log off;
  }

  # Stop processing if file exists
  if (-f $request_filename) {
    break;
  }
}

Result:

  • Any slug at the root level (/licenses, /about, etc.) automatically maps to your PHP page controller.

  • No hardcoding of id=X required.

  • Works with an unlimited number of pages.

Last updated