Difference between revisions of "Nginx"

From Organic Design wiki
(Our URL rewriting rules: &$args)
m (See also)
 
(7 intermediate revisions by the same user not shown)
Line 6: Line 6:
 
{{quote|Apache is like Microsoft Word, it has a million options but you only need six. Nginx does those six things, and it does five of them 50 times faster than Apache.|[https://chrislea.com/ Chris Lea]}}
 
{{quote|Apache is like Microsoft Word, it has a million options but you only need six. Nginx does those six things, and it does five of them 50 times faster than Apache.|[https://chrislea.com/ Chris Lea]}}
 
== Installation ==
 
== Installation ==
All our local installation documentation is in the [[install a new server]] procedure. The [[install a new server#selecting a good set of ciphers|selecting a good set of ciphers]] section covers more detail about the perfect forward secrecy issues and installation. And don't forget that {{h|'''cgi.fix_pathinfo''' must be set to ''false''}} in ''/etc/php5/fpm/php.ini'' to avoid [http://cnedelcu.blogspot.com.br/2010/05/nginx-php-via-fastcgi-important.html this] serious security issue that allows people to execute arbitrary code on the server.
+
All our local installation documentation is in the [[install a new server]] procedure. The [[install a new server#selecting a good set of ciphers|selecting a good set of ciphers]] section covers more detail about the perfect forward secrecy issues and installation. Note that the '''cgi.fix_pathinfo''' directive which used to be a critical security patch for Nginx has been [https://serverfault.com/questions/627903/is-the-php-option-cgi-fix-pathinfo-really-dangerous-with-nginx-php-fpm redundant] since about PHP version 5.3.
  
 
== Our URL rewriting rules ==
 
== Our URL rewriting rules ==
Line 35: Line 35:
 
Otherwise if the uri points to an existing file we use that, or as an overall default we treat the request as a friendly URL by rewriting to the script with the URI in the ''title'' query-string item. (we used to use the PATH_INFO form, but this fails for article that end in ''.php'').
 
Otherwise if the uri points to an existing file we use that, or as an overall default we treat the request as a friendly URL by rewriting to the script with the URI in the ''title'' query-string item. (we used to use the PATH_INFO form, but this fails for article that end in ''.php'').
 
<source lang="nginx">
 
<source lang="nginx">
try_files $wiki$uri $wiki$uri $wiki/wiki/index.php?title=$uri&$args;
+
try_files $wiki$uri $wiki$uri $wiki/wiki/index.php$request_uri;
 
</source>
 
</source>
 
 
That's it for the rewrite rules themselves which is nothing too problematic as it's really just a syntax change from our existing Apache based configuration file. But the real difficulty was to get Nginx to present an environment that matches that which Apache constructs so that the domain-based file-system mapping of our wiki farm works without requiring any change regardless of which web-server we're running. There were a number of challenges here which have all been encapsulated into a file called ''/var/www/work/nginx.php.conf'' which is used from any server block that wants to allow PHP execution and sets up the environment close enough to Apache to be compatible with our wikia structure.
 
 
=== Fixing SERVER_NAME ===
 
First To make a catch-all type system I've used the ''default_server'' option at the end of the ''listen'' directive and ommitted the ''server_name'' directive since its value is a literal copy of any wild-card or regular expression value it's given. To ensure we don't have an empty ''SERVER_NAME'' value in the PHP environment, I've set it to ''$host'' in our specific PHP ''fastcgi_params'' settings in ''nginx.php.conf''.
 
 
=== Fixing PATH_INFO ===
 
The next issue I came across is that there seems to be a problem with the ''PATH_INFO'' system where Nginx can't handle URLs that request a script with a continues path such as ''/foo.php/bar?biz''. I used [http://kbeezie.com/php-self-path-nginx/ KBeezie]'s solution which almost works, but it uses the [http://wiki.nginx.org/HttpFastcgiModule#fastcgi_split_path_info fastcgi_split_path_info] function which also seems to have a problem. It's supposed to accept a regular expression containing two captures, one gets assigned to the ''$fastcgi_script_name'' variable, and the other to ''$fastcgi_path_info'', but for some reason the former doesn't contain the path portion of the script even if the regular expression states that it should. So in our version of KBeezie's PHP configuration I've used the following custom variables instead of ''$fastcgi_script_name'' and ''$fastcgi_path_info''.
 
<source lang="nginx">
 
if ($uri ~ ^(.+\.php)(/?.*)$) {
 
    set $script $1;
 
    set $path $2;
 
}
 
</source>
 
'''Note:''' In addition to these changes, we need to set [http://www.mediawiki.org/wiki/Manual:$wgUsePathInfo $wgUsePathInfo] to ''true'' in some MediaWiki versions when using Nginx, for example it was needed in MediaWiki 1.19.2, but not in 1.20.3. There's no harm (in our wikia environment) in having it always set though so this has been added to our [http://svn.organicdesign.co.nz/filedetails.php?repname=extensions&path=%2Fwikia.php wikia.php] extension.
 
 
=== Fixing SCRIPT_NAME ===
 
This code block is at the start of our ''/var/www/work/nginx.php.conf'' file which is included from any of our server blocks that require PHP processing. But the block still required further adjusting as the ''SCRIPT_NAME'' parameter in this configuration was not matching what Apache was giving and this was causing Nginx to keep redirecting indefinitely. The problem is that ''SCRIPT_NAME'' includes the hostname e.g. ''/organicdesign.co.nz/wiki/index.php'' instead of just ''/wiki/index.php'' that Apache's ''SCRIPT_NAME'' would contain. This seems to be getting added to the ''$document_root'' value internally somehow, but only for the setting of ''SCRIPT_NAME''. So the initial patch at the start of ''nginx.php.conf'' is now as follow:
 
<source lang="nginx">
 
if ($uri ~ ^(.+?/)?(.+\.php)(.*)$) {
 
    set $first $1;
 
    set $script /$2;
 
    set $path $3;
 
}
 
if ($first != $wiki/) {
 
    set $script $first$script;
 
}
 
</source>
 
This effectively removes the beginning portion of ''$script'' if it starts with the same value as ''$wiki'' (which is set in the main server block to the name of the domain excluding ''www'' or ''wiki'' prefixes). The effected ''fastcgi_param'' names are now set as follows:
 
<source lang="nginx">
 
fastcgi_param  PATH_INFO          $path;
 
fastcgi_param  PATH_TRANSLATED    $document_root$wiki$script;
 
fastcgi_param  SCRIPT_NAME        $script;
 
fastcgi_param  SCRIPT_FILENAME    $document_root$wiki$script;
 
</source>
 
'''Note:''' The MediaWiki configuration variable [http://www.mediawiki.org/wiki/Manual:$wgServer $wgServer] seems to be getting incorrectly set sometimes when left to the default, so I've now set this manually in our [http://svn.organicdesign.co.nz/filedetails.php?repname=extensions&path=%2Fwikia.php wikia.php] extension.
 
  
 
=== Stand-alone and local wikis ===
 
=== Stand-alone and local wikis ===
Line 81: Line 44:
 
     listen 80;
 
     listen 80;
 
     server_name foo.bar;
 
     server_name foo.bar;
     include /var/www/work/nginx.php.conf;
+
     include /var/www/conf/nginx.php.conf;
     rewrite ^/$ /foo/index.php?title=Main_Page&redirect=no last;
+
     root /var/www/foo
    rewrite ^/files/thumb/./../(.+?)/(\d+)px- /foo/thumb.php?w=$2&f=$1 last;
+
     rewrite ^/$ /wiki/index.php?title=Main_Page&redirect=no last;
    if (-f $document_root/foo$uri) {
+
    rewrite ^/wiki/images/thumb/./../(.+?)/(\d+)px- /wiki/thumb.php?w=$2&f=$1 last;
        rewrite ^ /foo$uri last;
+
    try_files $uri $uri /wiki/index.php$request_uri;
    }
 
    rewrite ^ /foo/index.php?title=$uri last;
 
}
 
</source>
 
 
 
 
 
Here's a complete configuration file for a wiki served locally including friendly URLs and [[Extension:WebSocket|WebSocket]] support.
 
<source lang="nginx">
 
access_log /var/log/nginx/access.log;
 
 
 
server {
 
listen 80 default_server;
 
 
 
root /var/www;
 
index index.php;
 
client_max_body_size 8m;
 
fastcgi_read_timeout 300s;
 
 
 
location ~ websocket:([0-9]+) {
 
proxy_pass http://127.0.0.1:$1;
 
proxy_http_version 1.1;
 
proxy_set_header Upgrade websocket;
 
proxy_set_header Connection upgrade;
 
}
 
 
 
location ~ \.php {
 
if ($uri ~ ^(.+?/)?(.+\.php)(.*)$) {
 
set $first $1;
 
set $script $first$2;
 
set $path $3;
 
}
 
fastcgi_param  PATH_INFO          $path;
 
fastcgi_param  PATH_TRANSLATED    $document_root$script;
 
fastcgi_param  QUERY_STRING      $query_string;
 
fastcgi_param  REQUEST_METHOD    $request_method;
 
fastcgi_param  CONTENT_TYPE      $content_type;
 
fastcgi_param  CONTENT_LENGTH     $content_length;
 
fastcgi_param  SCRIPT_NAME        $script;
 
fastcgi_param  SCRIPT_FILENAME    $document_root$script;
 
fastcgi_param  REQUEST_URI        $request_uri;
 
fastcgi_param  DOCUMENT_ROOT      $document_root;
 
fastcgi_param  SERVER_PROTOCOL    $server_protocol;
 
fastcgi_param  GATEWAY_INTERFACE  CGI/1.1;
 
fastcgi_param  SERVER_SOFTWARE    nginx/$nginx_version;
 
fastcgi_param  REMOTE_ADDR        $remote_addr;
 
fastcgi_param  REMOTE_PORT        $remote_port;
 
fastcgi_param  SERVER_ADDR        $server_addr;
 
fastcgi_param  SERVER_PORT        $server_port;
 
fastcgi_param  SERVER_NAME        $host;
 
fastcgi_param  HTTP_REFERER      $http_referer;
 
fastcgi_param  HTTPS              $https;
 
fastcgi_pass                      unix:/var/run/php5-fpm.sock;
 
fastcgi_index                    index.php;
 
fastcgi_intercept_errors          on;
 
fastcgi_buffers                  8 16k;
 
fastcgi_buffer_size              32k;
 
}
 
 
 
rewrite ^/$ /wiki/index.php?title=Main_Page&redirect=no last;
 
rewrite ^/files/thumb/./../(.+?)/(\d+)px- /wiki/thumb.php?w=$2&f=$1 last;
 
if (-f $document_root$uri) { rewrite ^ $uri last; }
 
rewrite ^ /wiki/index.php$uri last;
 
 
}
 
}
</source>
 
 
== No WebDAV & Sbbversion ==
 
Nginx has almost complete WebDAV functionality when conpiled with the basic [http://wiki.nginx.org/HttpDavModule DavModule] for Nginx which has the ''PUT'', ''DELETE'', ''MKCOL'', ''COPY'' and ''MOVE'' methods, and the [https://github.com/arut/nginx-dav-ext-module nginx-dav-ext-module] which adds the missing ''PROPFIND'' and ''OPTIONS'' methods. The Debian package comes precompiled with both of these, but to handle [[Subversion]] requests directly a higher-level protocol called ''Delta-V'' is required which there is no support for in Nginx so Subversion access is currently not possible.
 
 
== Notes & gotchas  ==
 
Some important things to remember about Nginx request processing are:
 
*{{h|Remember that '''cgi.fix_pathinfo''' must be set to ''false'' in ''/etc/php5/fpm/php.ini'' to avoid [http://cnedelcu.blogspot.com.br/2010/05/nginx-php-via-fastcgi-important.html this] serious security issue that allows people to execute arbitrary code on the server.}}
 
*''$request_uri'' is the original request, ''$uri'' is updated after rewrites
 
 
The minimum required ''fastcgi'' parameters that allow a proper PHP request to be carried out are:
 
<source lang="nginx">
 
fastcgi_param  REQUEST_METHOD    $request_method;
 
fastcgi_param  CONTENT_TYPE      $content_type;
 
fastcgi_param  CONTENT_LENGTH    $content_length;
 
fastcgi_param  SCRIPT_NAME        $fastcgi_script_name;
 
fastcgi_param  SCRIPT_FILENAME    $document_root/$fastcgi_script_name;
 
 
</source>
 
</source>
  
Line 179: Line 63:
  
 
== See also ==
 
== See also ==
 +
*[[1 July 2013]] ''- our move over to Nginx''
 
*[[SSL]] ''- setting up SSL on Nginx, including generating a self-signed certificate and enabling perfect-forward-secrecy''
 
*[[SSL]] ''- setting up SSL on Nginx, including generating a self-signed certificate and enabling perfect-forward-secrecy''
*[[Enabling math markup#Nginx]] ''- there can be some diffciulty getting math to render, a workable solution is here''
+
*[[Enabling math markup#Nginx]] ''- there can be some difficulty getting math to render, a workable solution is here''
 
*[http://serverfault.com/questions/18994/nginx-best-practices Nginx best practices]
 
*[http://serverfault.com/questions/18994/nginx-best-practices Nginx best practices]
 
*[http://wiki.nginx.org/Pitfalls Nginx pitfalls]
 
*[http://wiki.nginx.org/Pitfalls Nginx pitfalls]
Line 186: Line 71:
 
*[http://www.hybridforge.com/blog/nginx-ssl-ciphers-and-pci-compliance More Nginx SSL advice]
 
*[http://www.hybridforge.com/blog/nginx-ssl-ciphers-and-pci-compliance More Nginx SSL advice]
 
*[http://wiki.nginx.org/DirectiveIndex Directive index]
 
*[http://wiki.nginx.org/DirectiveIndex Directive index]
*[http://wiki.nginx.org/HttpCoreModule#Variables Nginx variables]
+
*[https://nginx.org/en/docs/varindex.html Nginx variables]
 
*[http://wiki.nginx.org/HttpCoreModule#location The "location" directive]
 
*[http://wiki.nginx.org/HttpCoreModule#location The "location" directive]
*[http://nginx.org/en/docs/http/ngx_http_rewrite_module.html Conditions and their oeprators]
+
*[http://nginx.org/en/docs/http/ngx_http_rewrite_module.html Conditions and their operators]
 
*[http://wiki.nginx.org/HttpRewriteModule Nginx rewrite module]
 
*[http://wiki.nginx.org/HttpRewriteModule Nginx rewrite module]
 
*[http://agentzh.blogspot.com.br/2011/03/how-nginx-location-if-works.html Nginx's "if" directive explained in detail]
 
*[http://agentzh.blogspot.com.br/2011/03/how-nginx-location-if-works.html Nginx's "if" directive explained in detail]
Line 195: Line 80:
 
*[http://www.nginxtips.com/502-bad-gateway-using-nginx/ About 502 Bad Gateway errors]
 
*[http://www.nginxtips.com/502-bad-gateway-using-nginx/ About 502 Bad Gateway errors]
 
*[http://baudehlo.wordpress.com/2013/06/24/setting-up-perfect-forward-secrecy-for-nginx-or-stud/ How to set up PFS for Nginx]
 
*[http://baudehlo.wordpress.com/2013/06/24/setting-up-perfect-forward-secrecy-for-nginx-or-stud/ How to set up PFS for Nginx]
 +
*[https://thehackernews.com/2019/12/nginx-copyright-rumbler.html Russian Police Raided NGINX Moscow Office, Detained Co-Founders]
 
[[Category:Libre software]]
 
[[Category:Libre software]]

Latest revision as of 15:45, 18 July 2023

Nginx-logo.png

Nginx by all accounts is much more efficient than Apache, so on 1 July 2013 we migrated our server and server installation procedure over to Nginx.

Nginx uses an asynchronous event-driven approach to handling requests, instead of the Apache model that defaults to a threaded or process-oriented approach. Nginx's event-driven approach can provide more predictable performance under high loads.

Another reason we're moving over to Nginx is due to the recent interest in Perfect forward secrecy (PFS) coming from articles such as this. PFS is an obscure feature of SSL/TLS and requires at least OpenSSL version 1 and Apache version 2.3.3, but Nginx has supported it for quite some time now.

Quote.pngApache is like Microsoft Word, it has a million options but you only need six. Nginx does those six things, and it does five of them 50 times faster than Apache.
Chris Lea

Installation

All our local installation documentation is in the install a new server procedure. The selecting a good set of ciphers section covers more detail about the perfect forward secrecy issues and installation. Note that the cgi.fix_pathinfo directive which used to be a critical security patch for Nginx has been redundant since about PHP version 5.3.

Our URL rewriting rules

The OD server uses a rather complicated URL-rewriting system that allows all the wikis under all domains to run from a singe "catch-all" server block - or actually two, one for plain and one for SSL. This was quite difficult to replicate on Nginx such that it could work in exactly the same way and thereby be "web server agnostic".

The catch-all wiki rewrite rules apply if no other domain-based patterns have matched such as requests with an svn or webmail sub-domain prefix. Our configuration is rather "if" heavy which is strongly discouraged by Nginx gurus, but they're ok as long as they're not inside location context or contain only rewrite last directives or other non-content-handling operations such as set. Our ones here that are inside location context contain only set directives which should be safe. See this article for more detail about how the "if" directive works and why it can be so tricky to use in practice.

This first block sets variable called $wiki which will be used by other following conditions and location blocks (note that even if the .php block is included prior to these settings in the file, they are actually evaluated after them as these blocks are all outside of location scope. $wiki is the directory in which the wiki's file structure resides, i.e. one of the symlinks in /var/www/domains which are named to match the domain of the request. It is used in the following rules in the main scope and also by the nginx.php.conf include for setting the fastcgi_params. Note that this condition always applies and sets $wiki.

if ($host ~* ^(www\.|wiki\.)?(.+)$) {
     set $wiki /$2;
}


Then our first rewrite rule matches the root request with no path or file specified which gets rewritten to the wiki Main Page.

rewrite ^/$ $wiki/wiki/index.php?title=Main_Page&redirect=no last;


Next we need to check if the request is for an image thumbnail with dynamic sizing and if so, route the requests to the thumb.php script. This is simpler than on Apache because Nginx doesn't have the ampersand bug that requires an extra rule for dealing with thumbnails for filenames containing the ampersand symbol.

rewrite ^/files/thumb/./../(.+?)/(\d+)px- $wiki/wiki/thumb.php?w=$2&f=$1 last;


Otherwise if the uri points to an existing file we use that, or as an overall default we treat the request as a friendly URL by rewriting to the script with the URI in the title query-string item. (we used to use the PATH_INFO form, but this fails for article that end in .php).

try_files $wiki$uri $wiki$uri $wiki/wiki/index.php$request_uri;

Stand-alone and local wikis

For wikis that have their own code-base directory instead of using the shared code-bases of the wiki farm can use a block similar to the following example to do their friendly URL's.

server {
    listen 80;
    server_name foo.bar;
    include /var/www/conf/nginx.php.conf;
    root /var/www/foo
    rewrite ^/$ /wiki/index.php?title=Main_Page&redirect=no last;
    rewrite ^/wiki/images/thumb/./../(.+?)/(\d+)px- /wiki/thumb.php?w=$2&f=$1 last;
    try_files $uri $uri /wiki/index.php$request_uri;
}

Block processing order

  • try_files only does a redirect for the last parameter so others cannot be *.php as the php location won't be processed
  • Only one location block will match and be processed
  • The first exact match (using =) will return immediately
  • Next strings will be matched, the most specific match being chosen
  • Then regex matches will be chosen the first match overriding any string matches (strings can use ^~ to block the regex tests after a match)
  • Rewrites at server level are evaluated before the location directives are evaluated
  • Rewrites within location blocks are then evaluated
  • If rewrites within a location block change the URI, then the location directives are evaluated again

See also