Nginx Wiki - Drupal

We encourage users to post events happening in the community to the community events group on https://www.drupal.org.
MTecknology's picture

I took the time to write up a best practices wiki page on the Nginx wiki for Drupal. Your configurations may or may not be similar and may or may not add extras. The setup in the Nginx wiki will not cover things like using Boost or Varnish.

The configuration in the wiki will provide an excellent base for anything you want to do with Drupal (and Pressflow). It takes a lot of beginner mistakes and explains why they are the wrong way. There are a massive number of beginner mistakes that I've seen around drupal.org and the rest of the Internet. I hope that linking to this page will help.

Nginx Wiki - Drupal

Comments

I don't agree with

brianmercer's picture

I don't agree with everything, but it looks good. I like the multi-user separation for those folks who need that.

If you're using "fastcgi_split_path_info ^(.+\.php)(/.+)$;" (which isn't necessary for Drupal but some other software) then the location should be "location ~ ^(.+\.php)(.*)$;" to get a match.

What about protection against executing .php files in the files directory, or a more restrictive .php location. My own config is restricted to Drupal's index.php only and I use ssl/passworded alternate ports for admin software. see http://test.brianmercer.com/content/nginx-configuration-drupal.

Thanks for taking the time.

Avoid repetition of fastcgi related directives

perusio's picture

If you created a fastcgi directives file and just include it wherever necessary the file, the configuration could be shorter.

I concur with Brian that install.php e other sensitive files should be protected. Be it using the basic auth, or if you want to be in stronger security terms, using TLS. Other than that it's a nice addition to the nginx wiki. Well done.

Also a little security by obscurity in cron.php can be quite effective. If returning a 403 you let the attacker know that such a file exists.

I use the following:

# Restrict cron access to a specific host.
location /cron.php {
         allow 127.0.0.1;
         allow 192.168.1.0/24;
         error_page 403 =404;
         fastcgi_pass 127.0.0.1:9000;
         deny all;
}

I'll look into the unix socket vs. internet socket thing. Time to brush up on long ago and far away C skills.

Yeah, in my config I comment

brianmercer's picture

Yeah, in my config I comment out the section for install.php. I meant some protection against /var/www/drupal/sites/mysite.com/files/dobadthings.php being uploaded by someone and then run. Drupal core uses an .htaccess file for that. http://drupal.org/node/65409

My cron.php and update.php are not reachable externally. I do those through drush cronjobs and ssh to update.

Isn't that handled through MIME type checking?

perusio's picture

If you use both filefield and mimedetect for all uploads. I don't think you can "fool" the file utility on Unix or the Fileinfo extension in PHP to accept a PHP file as an image or so. Of course you have to restrict the types of files being uploaded in the Filefield configuration.

PDF files are a problem because there are a lot of exploits in the wild. Not so much for the server but for the client. Since Adobe just couldn't resist throwing everything and the kitchen sink in the PDF spec. Not only it has scripting abilities (JS) but also has external program launching capabilities :(

Cf. http://blog.didierstevens.com/programs/pdf-tools/

I agree that limiting .php files at the server level is a good idea. It's another layer of protection.

I never use update.php. I do all updates using drush. You just gave me an idea. Disable access do both install.php and update.php. No need for basic auth whatsoever. Thanks.

lazy server_name

mikeytown2's picture

running a multisite and you wish to redirect all www requests to the non www version.
http://wiki.nginx.org/Pitfalls#Server_Name
The solution given here is hard-coded to the domain; is there a good general solution? And to go along with that is there a general solution to server_name? would *.com work?

Well, I don't think so.

perusio's picture

According to http://nginx.org/en/docs/http/server_names.html, you need the something in the middle. You can omit the TLD or the specific hostname, but not the middle part. You can use regexes in the server name.

From the above page:

A wildcard name may contain an asterisk only on the name's start or end, and only on a dot border. The names “www.*.nginx.org” and “w*.nginx.org” are invalid. However, these names can be specified using regular expressions, for example, “~^www\..+\.nginx\.org$” and “~^w.*\.nginx\.org$”. An asterisk can match several name parts. The name “*.nginx.org” matches not only www.nginx.org but www.sub.nginx.org as well.

Although you can use a regex:

server_name  ~ ^(www.)?(?<domain>.+)$;

works. The domain name is stored in $domain.

If not using named capturing groups then:

server_name ~ ^(www.)?(.+)$

gets the domain name stored in $1 (numeric capturing group).

remove www without if

mikeytown2's picture

would this redirect www to non then? And then the 2nd one would be a catch all.

server {
  server_name  ~ ^(www.)?(?<domain>.+)$;
  rewrite ^ $scheme://$domain$request_uri permanent;
}
server {
  server_name  _;
}

Hmm

perusio's picture

I don't think that the catch all server_name directive is intended as a rewrite mechanism. Rather it's use is usually to constrain incorrect 'Host' headers in the request. For using it like that you must specify that the catch all server block is the default_server. Also server_name_in_redirect must be set to off. So that nginx doesn't use the invalid "_" for a redirect. Setting this directive to off makes nginx use the Host header. Note that you're exposing yourself to incorrect Host headers being sent. What if your code makes use of the

<?php
$_SERVER
['HTTP_HOST']
?>

global to load a file or whatever. An attacker can forge an header with a proper request URI so that bad things can happen.

server {
    server_name _ default_server;
    listen [::]:80;
    server_name_in_redirect off;
    (...)
}   

This is I what I think based in my very superficial knowledge of nginx configuration ins and outs.

HTTP_HOST

mikeytown2's picture

Thanks for server_name_in_redirect off;

if HTTP_HOST is "bad" then a lot of thing in Drupal land don't work correctly
http://api.drupal.org/api/function/drupal_valid_http_host/6 - Validates the hostname
http://api.drupal.org/api/function/conf_path/6 - if site is not in default then you will not get the right site. host is what loads up the correct settings.php. Apache lets odd host names through so doing it this way doesn't add any new bugs AFAIK.

I'm not

perusio's picture

talking specifically about Drupal. The drupal_valid_http_host only filters for improper Host headers, not incorrect ones. E.g., 'Host: evilsite.com'. It's perfectly valid.

Regarding the boostrap configuration phase, if a settings file referring to a specific host is not found it uses the default settings. So it will boostrap beyond the conf phase. I haven't looked into it, but if there's a module out there that lets someone use an incorrect Host header with a properly crafted request URI so that a file from a foreign host is requested and executed by the PHP interpreter than bad things can happen. This is highly speculative and I suppose unlikely. But nevertheless, I think that constraining the Host header as indicated here, http://nginx.org/en/docs/http/request_processing.html, is a good thing.

Thanks a lot for this!

apperceptions's picture

I was wondering about all the "if's" I was seeing in other examples after reading http://wiki.nginx.org/IfIsEvil

Stick to using rewrite or

brianmercer's picture

Stick to using rewrite or return directives inside an "if" and avoid using "if" inside an "if" or within a regex location. (and don't nest a regex location within a regex location)