While nginx is highly configurable, you might encounter some problems and pitfalls when configuring it with Drupal. This post contains our configuration and solutions to those problems.

Before reading on please consider the original resources in the nginx wiki or some other suggested configurations, e.g. this one on Github. Some problems we found:

  • nginx does extra URL encoding when passing query parameters to Drupal. This is bad especially for "+" characters in queries that do not translate to spaces in Drupal (as reported here). Therefore we cannot use the generic "location /" directive but use a generic regular expression to extract the query, in this case the "$1" variable is not encoded by nginx.
  • Private files cannot be downloaded fluently, i.e. they often pause for 30 seconds before they continue. It seems to be a problem related to HTTP keep-alive connections, because disabling them with "keepalive_requests 0;" solved the issue.
  • Image cache may cause problems if the images are not generated yet, so an additional "try_files $uri @drupal" statement for images redirects the request to Drupal in case an image is not found. Also works for Drupal 7, where image cache is part of Drupal core.

The strategy in the configuration is to first handle/secure files (robots.txt, PHP files), then protect or configure other relevant paths and to have a catch-all directive at the end. Here is the content of drupal.conf that can be included in a "server{}" block:

# common Drupal configuration options.
# Make sure to set $socket to a fastcgi socket.

        location = /favicon.ico {                 log_not_found off;                 access_log off;         }

        ###         ### support for http://drupal.org/project/robotstxt module         ###         location = /robots.txt {                 access_log off;                 try_files $uri @drupal;         }

        # no access to php files in subfolders.         location ~ .+/.*.php$ {                 return 403;         }

        location ~* .(inc|engine|install|info|module|sh|sql|theme|tpl.php|xtmpl|Entries|Repository|Root|jar|java|class)$ {                 deny all;         }

        location ~ .php$ {                 # Required for private files, otherwise they slow down extremely.                 keepalive_requests 0;                 include fastcgi.conf;         }

        # private files protection         location ~ ^/sites/.*/private/ {                 access_log off;                 deny all;         }

        location ~* ^(?!/system/files).*.(js|css|png|jpg|jpeg|gif|ico)$ {                 # If the image does not exist, maybe it must be generated by drupal (imagecache)                 try_files $uri @drupal;                 expires 7d;                 log_not_found off;         }

        ###         ### deny direct access to backups         ###         location ~* ^/sites/.*/files/backup_migrate/ {                 access_log off;                 deny all;         }

        location ~ ^/(.*) {                 try_files $uri /index.php?q=$1&$args;         }

        location @drupal {                 # Some modules enforce no slash (/) at the end of the URL                 # Else this rewrite block wouldn't be needed (GlobalRedirect)                 rewrite ^/(.*)$ /index.php?q=$1;         }

And here is the configuration for passing the request to PHP FastCGI (fastcgi.conf):

# common fastcgi configuration for PHP files

                fastcgi_split_path_info ^(.+.php)(/.+)$;                 #NOTE: You should have "cgi.fix_pathinfo = 0;" in php.ini                 include fastcgi_params;                 fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;                 fastcgi_intercept_errors on;                 fastcgi_pass $socket;                 # workaround as fastcgi_param cannot be used inside if statements                 set $https "";                 if ($scheme = https) {                   set $https on;                 }                 fastcgi_param HTTPS $https;                 fastcgi_read_timeout 6000;                 # set expires header for private files.                 if ($args ~* .(js|css|png|jpg|jpeg|gif|ico)) {                     expires 7d;                 }