Nginx: Difference between revisions

From MoodleDocs
The guide had incorrect alias definition. I had to add a different alias definition of NGINX to it work correctly. This solves https://moodle.org/mod/forum/discuss.php?d=349925 and https://moodle.org/mod/forum/discuss.php?d=413492
No edit summary
 
(12 intermediate revisions by 9 users not shown)
Line 1: Line 1:
{{Installing Moodle}}[[Nginx]] [engine x] is an HTTP and reverse proxy server, as well as a mail proxy server, written by Igor Sysoev. The nginx project started with a strong focus on high concurrency, high performance and low memory usage. It is licensed under the 2-clause BSD-like license and it runs on Linux, BSD variants, Mac OS X, Solaris, AIX, HP-UX, as well as on other *nix flavours. It also has a proof of concept port for Microsoft Windows.
{{Installing Moodle}}{{Update}}[[Nginx]] [engine x] is an HTTP and reverse proxy server, as well as a mail proxy server, written by Igor Sysoev. The nginx project started with a strong focus on high concurrency, high performance and low memory usage. It is licensed under the 2-clause BSD-like license and it runs on Linux, BSD variants, Mac OS X, Solaris, AIX, HP-UX, as well as on other *nix flavours. It also has a proof of concept port for Microsoft Windows.


''The following is community-contributed documentation on Nginx configuration. Amendments and additions are welcome.''
''The following is community-contributed documentation on Nginx configuration. Amendments and additions are welcome.''
== Nginx configuration ==
== Nginx configuration ==
=== PHP-FPM ===
=== PHP-FPM ===
Nginx is usually configured to interface with PHP via [http://php.net/manual/en/install.fpm.php php-fpm]. This is both fast and robust.
Nginx is usually configured to interface with PHP via [http://php.net/manual/en/install.fpm.php php-fpm]. This is both fast and robust.


PHP-FPM's default behaviour for pools is usually to restrict the execution of scripts to a specific extension, i.e. .php. You should ensure that this behaviour is configured within your particular package/distribution, e.g. for debian,
PHP-FPM's default behaviour for pools is usually to restrict the execution of scripts to a specific extension, i.e. .php. You should ensure that this behaviour is configured within your particular package/distribution, e.g. for debian,


'''/etc/php5/fpm/pool.d/www.conf'''
'''/etc/php/8.2/fpm/pool.d/www.conf'''
  security.limit_extensions = .php
  security.limit_extensions = .php
=== Nginx ===
=== Nginx ===


Since version 4.5, Moodle makes use of both 'slash arguments', and a Routing Engine.
Both of these require that you correctly configure your server to correctly split on the filename. The use of the <code>try_files</code> directive is also known to interfere with the FastCGI configuration unless properly configured.
If not correctly configured, you will see issues such as missing images and CSS.{{Note|Since Moodle 5.1, a '''/public''' directory has been added to the codebase. Nginx must be configured so that the document root points to this directory (e.g. <code>root /srv/moodle/public;</code>). This ensures only web-accessible files are served. See the [https://moodledev.io/docs/5.1/guides/restructure restructure guide] for more details.}}
==== FastCGI Configuration ====
The following configuration is known to work with Moodle 4.5 and above.
<syntaxhighlight lang="nginx">
location ~ \.php(/|$) {
  # Split the path info based on URI.
  fastcgi_split_path_info ^(.+\.php)(/.*)$;
  # Note: Store the original path_info. It will be wiped out in a moment by try_files.
  set $path_info $fastcgi_path_info;
  # Look for the php file, trying a trailing slash for directories if required.
  # Finally, send the request to the router - r.php - as a fallback.
  try_files $fastcgi_script_name $fastcgi_script_name/ /r.php$is_args$args;
  # File was found - pass to fastcgi.
  fastcgi_pass  127.0.0.1:9000;
  # Alternately, pass to unix socket (depends on pool listen configuration)
  # fastcgi_pass unix:/var/run/php/php8.2-fpm.sock;


Add the following 'slash arguments' compatible 'location' block to your vhosts 'server' in your nginx configuration (further explanation at '[[Using slash arguments]]').
  include        fastcgi_params;


'''nginx.conf location:'''
  # Re-apply the path_info after including fastcgi_params.
<pre>
  fastcgi_param PATH_INFO $path_info;
location ~ [^/]\.php(/|$) {
  fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
    fastcgi_split_path_info  ^(.+\.php)(/.+)$;
  fastcgi_param DOCUMENT_ROOT $realpath_root;
    fastcgi_index            index.php;
    fastcgi_pass            127.0.0.1:9000 (or your php-fpm socket);
    include                  fastcgi_params;
    fastcgi_param   PATH_INFO       $fastcgi_path_info;
    fastcgi_param   SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
}
</pre>
</syntaxhighlight>
If the above does not work try the following:
 
Note: This was for CentOS 7.6 (1804), MariaDB 10.3, Nginx 1.15 and PHP 7.3.5
==== Routing Engine ====
<pre>
location ~ ^(.+\.php)(.*)$ {
    root /usr/share/nginx/html/moodle/;
    fastcgi_split_path_info  ^(.+\.php)(.*)$;
    fastcgi_index            index.php;
    fastcgi_pass            127.0.0.1:9000;
    include /etc/nginx/mime.types;
    include                  fastcgi_params;
    fastcgi_param  PATH_INFO      $fastcgi_path_info;
    fastcgi_param  SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
</pre>


If your Moodle site is hosted from the root location, you can use the following configuration to route requests to the correct file.


If you find that this does not work (scripts, styles, and images not loading) '''and''' that there are <code>open() "..." failed (20: Not a directory)</code> lines appearing in your logs: Check whether there are any directives related to static content '''before''' this block and try moving them '''after''' this block.
<syntaxhighlight lang="nginx">
location / {
    try_files $uri $uri/ /r.php$is_args$args;
}
</syntaxhighlight>


Where your Moodle site is hosted in a sub-directory, you must specify the sub-directory in the location block.
<syntaxhighlight lang="nginx">
location /moodle/ {
  try_files $uri $uri/ /moodle/r.php$is_args$args;
}
</syntaxhighlight>


Another potential pitfall is the use of '''try_files'''. Many guides recommend configurations like <pre>try_files $fastcgi_script_name =404;</pre>. But if try_files is used, nginx deletes the content of the '''$fastcgi_path_info''' variable (this is [https://trac.nginx.org/nginx/ticket/321 intended behavior]). This causes '''PATH_INFO''' to also be empty, so slash arguments don't work.
An alternative is the use of <pre>if(!-f $document_root$fastcgi_script_name) {return 404;}</pre> [https://www.nginx.com/resources/wiki/start/topics/examples/phpfcgi/#connecting-nginx-to-php-fpm as recommended] by the nginx documentation, although the nginx documentation also recommends [https://www.nginx.com/resources/wiki/start/topics/depth/ifisevil/ not using if].
==== Error pages ====
<pre>
# This passes 404 pages to Moodle so they can be themed
error_page 404 /error/index.php;    error_page 403 =404 /error/index.php;
</pre>
==== Hiding internal files ====
==== Hiding internal files ====
<pre>
<syntaxhighlight lang="nginx">
# Hide all dot files but allow "Well-Known URIs" as per RFC 5785
# Hide all dot files but allow "Well-Known URIs" as per RFC 5785  
location ~ /\.(?!well-known).* {
location ~ /\.(?!well-known).* {
     return 404;
     return 404;
}
}  
 
# This should be after the php fpm rule and very close to the last nginx ruleset.
# This should be after the php fpm rule and very close to the last nginx ruleset.  
# Don't allow direct access to various internal files. See MDL-69333
# Don't allow direct access to various internal files. See MDL-69333  
location ~ (/vendor/|/node_modules/|composer\.json|/readme|/README|readme\.txt|/upgrade\.txt|db/install\.xml|/fixtures/|/behat/|phpunit\.xml|\.lock|environment\.xml) {
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;
     deny all;
     return 404;
     return 404;  
}
}
</pre>
</syntaxhighlight>


==== XSendfile aka X-Accel-Redirect ====


==== XSendfile aka X-Accel-Redirect ====
Setting Moodle and Nginx to use XSendfile functionality is a big win as it frees PHP from delivering files allowing Nginx to do what it does best, i.e. deliver files.  
Setting Moodle and Nginx to use XSendfile functionality is a big win as it frees PHP from delivering files allowing Nginx to do what it does best, i.e. deliver files.  


Enable xsendfile for Nginx in Moodles config.php, this is documented in the config-dist.php, a minimal configuration look like this,
Enable xsendfile for Nginx in Moodles config.php, this is documented in the config-dist.php, a minimal configuration look like this,<syntaxhighlight lang="nginx">
<pre>
$CFG->xsendfile = 'X-Accel-Redirect';
$CFG->xsendfile = 'X-Accel-Redirect';
$CFG->xsendfilealiases = array(
$CFG->xsendfilealiases = array(
     '/dataroot/' => $CFG->dataroot
     '/dataroot/' => $CFG->dataroot
);
);
</pre>
</syntaxhighlight>Accompany this with a matching 'location' block in your nginx server configuration.<syntaxhighlight lang="nginx">
Accompany this with a matching 'location' block in your nginx server configuration.
location /dataroot/ {
<pre>
location ~ ^/dataroot/(.*)$ {
    # The definition of 'internal' here is CRITICAL as it prevents client access to your dataroot.
     internal;
     internal;
     alias <full_moodledata_path>/$1; # ensure the path ends with '/$1'
     alias <full_moodledata_path>; # ensure the path ends with /
}
}
</syntaxhighlight>The definition of 'internal' here is '''critical''' as it prevents client access to your dataroot.
==== Load Balancer Hints (AWS) ====
If you're using an AWS load balancer in front your infrastructure, you can set up some of the configuration above, preventing traffic to go forward. Here is the configuration applied to hide files, with a few considerations due to known limitations:
* 100 total rules per application load balancer
* 5 condition values per rule
* 5 wildcards per rule
* 5 weighted target groups per rule:
<small>
<pre>
[
    {
        "Conditions": [
            {
                "Field": "path-pattern",
                "Values": [
                    "*/.*",
                    "*/upgrade.txt",
                    "*/db/install.xml",
                    "*/README.md"
                ],
                "PathPatternConfig": {
                    "Values": [
                        "*/.*",
                        "*/upgrade.txt",
                        "*/db/install.xml",
                        "*/README.md"
                    ]
                }
            }
        ],
        "Actions": [
            {
                "Type": "fixed-response",
                "Order": 1,
                "FixedResponseConfig": {
                    "ContentType": "text/html",
                    "MessageBody": "<html>\n<head><title>404 Not Found</title></head>\n<body>\n<center><h1>404 Not Found</h1></center>\n<hr>\n</body>\n</html>",
                    "StatusCode": "404"
                }
            }
        ]
    },
    {
        "Conditions": [
            {
                "Field": "path-pattern",
                "Values": [
                    "*/composer.json",
                    "*/Gruntfile.js",
                    "*.lock",
                    "*/environtment.xml",
                    "*/readme.txt"
                ],
                "PathPatternConfig": {
                    "Values": [
                        "*/composer.json",
                        "*/Gruntfile.js",
                        "*.lock",
                        "*/environtment.xml",
                        "*/readme.txt"
                    ]
                }
            }
        ],
        "Actions": [ #### Same as above
            ...
        ]
    },
    {
        "Conditions": [
            {
                "Field": "path-pattern",
                "Values": [
                    "*/fixtures/*",
                    "*/behat/*",
                    "*/phpunit.xml"
                ],
                "PathPatternConfig": {
                    "Values": [
                        "*/fixtures/*",
                        "*/behat/*",
                        "*/phpunit.xml"
                    ]
                }
            }
        ],
        "Actions": [ #### Same as above
            ...
        ]
    }
]
</pre></small>


</pre>
The definition of 'internal' here is '''critical''' as it prevents client access to your dataroot.
== See also ==
== See also ==
* Real <tt>PATH_INFO</tt> support:
* Real <tt>PATH_INFO</tt> support:
** https://moodle.org/mod/forum/discuss.php?d=278916
** https://moodle.org/mod/forum/discuss.php?d=278916
Line 94: Line 198:
* '''[Deprecated]''' Internal rewriting to the HTTP GET <tt>file</tt> parameter:
* '''[Deprecated]''' Internal rewriting to the HTTP GET <tt>file</tt> parameter:
** https://moodle.org/mod/forum/discuss.php?d=83445
** https://moodle.org/mod/forum/discuss.php?d=83445
[[Category:Installation]]
[[Category:Installation]]
[[es:Nginx]]
[[es:Nginx]]
[[pt-br:Nginx]]
[[pt-br:Nginx]]
[[de:Nginx]]

Latest revision as of 11:29, 16 December 2025

This page requires updating. Please do so and remove this template when finished.

Nginx [engine x] is an HTTP and reverse proxy server, as well as a mail proxy server, written by Igor Sysoev. The nginx project started with a strong focus on high concurrency, high performance and low memory usage. It is licensed under the 2-clause BSD-like license and it runs on Linux, BSD variants, Mac OS X, Solaris, AIX, HP-UX, as well as on other *nix flavours. It also has a proof of concept port for Microsoft Windows.

The following is community-contributed documentation on Nginx configuration. Amendments and additions are welcome.

Nginx configuration

PHP-FPM

Nginx is usually configured to interface with PHP via php-fpm. This is both fast and robust.

PHP-FPM's default behaviour for pools is usually to restrict the execution of scripts to a specific extension, i.e. .php. You should ensure that this behaviour is configured within your particular package/distribution, e.g. for debian,

/etc/php/8.2/fpm/pool.d/www.conf

security.limit_extensions = .php

Nginx

Since version 4.5, Moodle makes use of both 'slash arguments', and a Routing Engine.

Both of these require that you correctly configure your server to correctly split on the filename. The use of the try_files directive is also known to interfere with the FastCGI configuration unless properly configured.

If not correctly configured, you will see issues such as missing images and CSS.

Note: Since Moodle 5.1, a /public directory has been added to the codebase. Nginx must be configured so that the document root points to this directory (e.g. root /srv/moodle/public;). This ensures only web-accessible files are served. See the restructure guide for more details.

FastCGI Configuration

The following configuration is known to work with Moodle 4.5 and above.

location ~ \.php(/|$) {
  # Split the path info based on URI.
  fastcgi_split_path_info ^(.+\.php)(/.*)$;

  # Note: Store the original path_info. It will be wiped out in a moment by try_files.
  set $path_info $fastcgi_path_info;

  # Look for the php file, trying a trailing slash for directories if required. 
  # Finally, send the request to the router - r.php - as a fallback.
  try_files $fastcgi_script_name $fastcgi_script_name/ /r.php$is_args$args;

  # File was found - pass to fastcgi.
  fastcgi_pass   127.0.0.1:9000;
  # Alternately, pass to unix socket (depends on pool listen configuration)
  # fastcgi_pass unix:/var/run/php/php8.2-fpm.sock;

  include        fastcgi_params;

  # Re-apply the path_info after including fastcgi_params.
  fastcgi_param PATH_INFO $path_info;
  fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
  fastcgi_param DOCUMENT_ROOT $realpath_root;
}

Routing Engine

If your Moodle site is hosted from the root location, you can use the following configuration to route requests to the correct file.

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

Where your Moodle site is hosted in a sub-directory, you must specify the sub-directory in the location block.

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

Hiding internal files

# Hide all dot files but allow "Well-Known URIs" as per RFC 5785 
location ~ /\.(?!well-known).* {
    return 404;
} 

# This should be after the php fpm rule and very close to the last nginx ruleset. 
# Don't allow direct access to various internal files. See MDL-69333 
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; 
}

XSendfile aka X-Accel-Redirect

Setting Moodle and Nginx to use XSendfile functionality is a big win as it frees PHP from delivering files allowing Nginx to do what it does best, i.e. deliver files.

Enable xsendfile for Nginx in Moodles config.php, this is documented in the config-dist.php, a minimal configuration look like this,

$CFG->xsendfile = 'X-Accel-Redirect';
$CFG->xsendfilealiases = array(
    '/dataroot/' => $CFG->dataroot
);

Accompany this with a matching 'location' block in your nginx server configuration.

location /dataroot/ {
    internal;
    alias <full_moodledata_path>; # ensure the path ends with /
}

The definition of 'internal' here is critical as it prevents client access to your dataroot.

Load Balancer Hints (AWS)

If you're using an AWS load balancer in front your infrastructure, you can set up some of the configuration above, preventing traffic to go forward. Here is the configuration applied to hide files, with a few considerations due to known limitations:

  • 100 total rules per application load balancer
  • 5 condition values per rule
  • 5 wildcards per rule
  • 5 weighted target groups per rule:

[
    {
        "Conditions": [
            {
                "Field": "path-pattern", 
                "Values": [
                    "*/.*", 
                    "*/upgrade.txt", 
                    "*/db/install.xml",
                    "*/README.md"
                ], 
                "PathPatternConfig": {
                    "Values": [
                        "*/.*", 
                        "*/upgrade.txt", 
                        "*/db/install.xml",
                        "*/README.md"
                    ]
                }
            }
        ], 
        "Actions": [
            {
                "Type": "fixed-response", 
                "Order": 1, 
                "FixedResponseConfig": {
                    "ContentType": "text/html", 
                    "MessageBody": "<html>\n<head><title>404 Not Found</title></head>\n<body>\n<center><h1>404 Not Found</h1></center>\n<hr>\n</body>\n</html>", 
                    "StatusCode": "404"
                }
            }
        ]
    }, 
    {
        "Conditions": [
            {
                "Field": "path-pattern", 
                "Values": [
                    "*/composer.json", 
                    "*/Gruntfile.js",
                    "*.lock", 
                    "*/environtment.xml",
                    "*/readme.txt"
                ], 
                "PathPatternConfig": {
                    "Values": [
                        "*/composer.json", 
                        "*/Gruntfile.js",
                        "*.lock", 
                        "*/environtment.xml",
                        "*/readme.txt"
                    ]
                }
            }
        ], 
        "Actions": [ #### Same as above
            ...
        ]
    }, 
    {
        "Conditions": [
            {
                "Field": "path-pattern", 
                "Values": [
                    "*/fixtures/*", 
                    "*/behat/*", 
                    "*/phpunit.xml"
                ], 
                "PathPatternConfig": {
                    "Values": [
                        "*/fixtures/*", 
                        "*/behat/*", 
                        "*/phpunit.xml"
                    ]
                }
            }
        ], 
        "Actions": [ #### Same as above
            ...
        ]
    }
]

See also