Difference between revisions of "Nginx"

From Organic Design wiki
m (Fixing PATH_INFO)
m (See also)
 
(54 intermediate revisions by 2 users not shown)
Line 1: Line 1:
Nginx by all accounts is much more efficient than Apache, so we will probably start changing the OD server, and our [[install a new server|server installation procedure]] over to Nginx. Currently (as of June 2013) we're running both Apache and Nginx with the former on ports 80 and 443 and the latter on 8080 and 8989. This way I can get the entire file structure of all sites running properly on Nginx without any change to the live sites on the standard ports. When everything is working I can switch Nginx over to the standard ports and then stop Apache and eventually un-install it completely if Nginx works out well.
+
[[File:Nginx-logo.png|375px|right|link=http://wiki.nginx.org]][http://wiki.nginx.org Nginx] by all accounts is much more efficient than [[Apache]], so on [[1 July 2013]] we migrated our server and [[install a new server|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.
 
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 [[w:Perfect forward secrecy|Perfect forward secrecy]] (PFS) coming from articles such as [http://news.netcraft.com/archives/2013/06/25/ssl-intercepted-today-decrypted-tomorrow.html 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.
 
Another reason we're moving over to Nginx is due to the recent interest in [[w:Perfect forward secrecy|Perfect forward secrecy]] (PFS) coming from articles such as [http://news.netcraft.com/archives/2013/06/25/ssl-intercepted-today-decrypted-tomorrow.html 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|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.
+
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 ==
 
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 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".
  
Som important things to remember about Nginx request processing are:
+
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 [http://wiki.nginx.org/IfIsEvil 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 [http://agentzh.blogspot.com.br/2011/03/how-nginx-location-if-works.html this article] for more detail about how the "if" directive works and why it can be so tricky to use in practice.
*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 thenn evaluated
 
*If rewrites within a location block change the URI, then the locations is evaluated again
 
*''$request_uri'' is the original request, ''$uri'' is updated after rewrites
 
  
=== SERVER_NAME ===
+
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''.
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 ''/var/www/work/nginx.php.conf''.
+
<source lang="nginx">
 
 
=== Fixing PATH_INFO ===
 
The first 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''.
 
{{code|<pre>
 
if ($uri ~ ^(.+\.php)(/?.*)$) {
 
    set $script $1;
 
    set $path $2;
 
}
 
</pre>}}
 
'''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:
 
{{code|<pre>
 
if ($uri ~ ^(.+?/)?(.+\.php)(.*)$) {
 
    set $first $1;
 
    set $script /$2;
 
    set $path $3;
 
}
 
if ($first != $wiki/) {
 
    set $script $first$script;
 
}
 
</pre>}}
 
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:
 
{{code|<pre>
 
fastcgi_param  PATH_INFO          $path;
 
fastcgi_param  PATH_TRANSLATED    $document_root$wiki$script$path;
 
fastcgi_param  SCRIPT_NAME        $script;
 
fastcgi_param  SCRIPT_FILENAME    $document_root$wiki$script;
 
</pre>}}
 
'''Note:''' The MediaWiki configuration variable ''$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.
 
 
 
=== The rewrite rules ===
 
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 [http://wiki.nginx.org/IfIsEvil strongly discouraged] by Nginx gurus, but apparently they're no problem as long as they contain only ''rewrite'' directives. The ones here that contain ''set'' directives are actually all unconditional since those conditions always evaluate to true so hopefully this avoids the dangerous "if" pitfalls that often occur in the Nginx world.
 
 
 
The first blocks set some variables 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. This one sets the ''$wiki'' value which 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.
 
{{code|<pre>
 
 
if ($host ~* ^(www\.|wiki\.)?(.+)$) {
 
if ($host ~* ^(www\.|wiki\.)?(.+)$) {
 
     set $wiki /$2;
 
     set $wiki /$2;
 
}
 
}
</pre>}}
+
</source>
  
  
The next is the ''$script'' variable which is the script name without PATH_INFO part but including prior path elements for example ''/wiki/index.php''.
+
Then our first rewrite rule matches the root request with no path or file specified which gets rewritten to the wiki Main Page.
{{code|<pre>
+
<source lang="nginx">
if ($uri ~ ^(.+\.php)?) {
+
rewrite ^/$ $wiki/wiki/index.php?title=Main_Page&redirect=no last;
    set $script $1;
+
</source>
}
 
</pre>}}
 
  
  
Then our first rewrite rule matches the root request with no path or file specified which gets rewritten to the wiki Main Page.
+
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.
{{code|<pre>
+
<source lang="nginx">
if ($uri ~ ^/$) {
+
rewrite ^/files/thumb/./../(.+?)/(\d+)px- $wiki/wiki/thumb.php?w=$2&f=$1 last;
    rewrite ^ $wiki/wiki/index.php?title=Main_Page&redirect=no last;
+
</source>
}
 
</pre>}}
 
 
 
  
{{h|Note, we need a couple more rules here for handling thumbnails which will be added soon.}}
 
  
 +
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">
 +
try_files $wiki$uri $wiki$uri $wiki/wiki/index.php$request_uri;
 +
</source>
  
If the requested URI maps to an existing file in the wiki structure, then we rewrite it to point to that.
+
=== Stand-alone and local wikis ===
{{code|<pre>
+
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.
if (-f $document_root$wiki$script) {
+
<source lang="nginx">
     rewrite ^ $wiki$uri last;
+
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;
 
}
 
}
</pre>}}
+
</source>
  
 
+
== Block processing order ==
Otherwise as an overall default we treat the request as a friendly URL by rewriting to the script with the URI in the PATH_INFO for the wiki to evaluate an article title from.
+
*''try_files'' only does a redirect for the last parameter so others cannot be *.php as the php ''location'' won't be processed
{{code|<pre>
+
*Only one location block will match and be processed
rewrite ^ $wiki/wiki/index.php$uri last;
+
*The first exact match (using =) will return immediately
</pre>}}
+
*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)
== FastCGI parameters ==
+
*Rewrites at server level are evaluated '''before''' the location directives are evaluated
The minimum required ''fastcgi'' parameters that allow a proper PHP request to be carried out are:
+
*Rewrites within location blocks are then evaluated
{{code|<pre>
+
*If rewrites within a location block change the URI, then the ''location'' directives are evaluated again
fastcgi_param  REQUEST_METHOD    $request_method;
 
fastcgi_param  SCRIPT_NAME        $fastcgi_script_name;
 
fastcgi_param  SCRIPT_FILENAME    $document_root/$fastcgi_script_name;
 
</pre>}}
 
  
 
== See also ==
 
== See also ==
*[http://wiki.nginx.org/LikeApache Config example for Apache people]
+
*[[1 July 2013]] ''- our move over to Nginx''
*[http://articles.slicehost.com/2007/12/19/ubuntu-gutsy-self-signed-ssl-certificates-and-nginx Self-signed certs in Nginx]
+
*[[SSL]] ''- setting up SSL on Nginx, including generating a self-signed certificate and enabling perfect-forward-secrecy''
*[http://www.westphahl.net/blog/2012/01/03/setting-up-https-with-nginx-and-startssl/ StartSSL & Nginx]
+
*[[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]
 
*[http://nginx.org/en/docs/http/configuring_https_servers.html Configuring Nginx HTTPS servers]
 
*[http://nginx.org/en/docs/http/configuring_https_servers.html Configuring Nginx HTTPS servers]
*[http://www.webhostingtalk.com/showthread.php?t=1025286 Installation on Debian]
+
*[http://www.hybridforge.com/blog/nginx-ssl-ciphers-and-pci-compliance More Nginx SSL advice]
*[http://wiki.nginx.org/HttpCoreModule#Variables Nginx variables]
+
*[http://wiki.nginx.org/DirectiveIndex Directive index]
 +
*[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://www.westphahl.net/blog/2012/01/03/setting-up-https-with-nginx-and-startssl/ StartSSL & Nginx]
 +
*[http://cnedelcu.blogspot.com.br/2010/05/dealing-with-nginx-400-bad-request-http.html Dealing with 400 Bad Request 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]
 +
*[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