Default NGINX File for a Drupal 6/7/8 Site on a Virtualmin Server


Although NGINX doesn’t have the concept of .htaccess, getting Drupal 8 working on a Virtualmin NGINX certainly is possible. You have to take the default NGINX conf file produced for every new NGINX virtual server, and then append the official NGINX recipe to the end. There are three other changes also documented below.

This guide has mostly been tested for Drupal 8, but it might also work with other versions. The three changes to be made are:

1. Ignore root directory path location.

In the Drupal NGINX recipe, ignore `root /var/www/drupal8; ## <– Your only path reference.` as this will already be set for you in Virtualmin.

The line from the default recipe that you don’t need, is the one that says root (3rd line from the top). The reason why you can exclude this from the official NGINX recipe is because Virtualmin already has it in and then some (DOCUMENT_ROOT & SCRIPT_FILENAME).

2. In the Drupal NGINX recipe, the socket location can be ignored. This has already been set in Virtualmin. The line to commented out is this one:

#fastcgi_pass unix:/var/run/php/php7.0-fpm.sock;

3. There is one other change that you might have to do. If you get An AJAX HTTP error occurred 404 for core/authorize.php/core/authorize.php then make a note of the rewrite addition below as per comment in this article.

rewrite ^/core/authorize.php/core/authorize.php(.*)$ /core/authorize.php$1;

Continuing on, if you look below, everything below fastcgi_read_timeout 90; is the official recipe including the above line and everything above fastcgi_read_timeout 90; is the default Virtualmin setup. We included a bigger timeout value as an example of a configuration value that one might do post Drupal install.

Without further adieu, here it is:

server {
   server_name mysite.com www.mysite.com;
   listen 102.x.y.z;
   root /home/bdotest/public_html;
   index index.html index.htm index.php;
   access_log /var/log/virtualmin/mysite.com_access_log;
   error_log /var/log/virtualmin/mysite.com_error_log;
   fastcgi_param GATEWAY_INTERFACE CGI/1.1;
   fastcgi_param SERVER_SOFTWARE nginx;
   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_FILENAME /home/mysite/public_html$fastcgi_script_name;
   fastcgi_param SCRIPT_NAME $fastcgi_script_name;
   fastcgi_param REQUEST_URI $request_uri;
   fastcgi_param DOCUMENT_URI $document_uri;
   fastcgi_param DOCUMENT_ROOT /home/mysite/public_html;
   fastcgi_param SERVER_PROTOCOL $server_protocol;
   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 $server_name;
   fastcgi_param HTTPS $https;
   location ~ \.php$ {
      try_files $uri =404;
      fastcgi_pass unix:/var/php-nginx/159437873153436.sock/socket;
   listen 102.x.y.z:443 ssl;
   ssl_certificate /home/bdotest/ssl.combined;
   ssl_certificate_key /home/bdotest/ssl.key;

   fastcgi_read_timeout 90;

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

   location = /robots.txt {
      allow all;
      log_not_found off;
      access_log off;

   # Very rarely should these ever be accessed outside of your lan
   location ~* \.(txt|log)$ {
      deny all;

   # See comments section on https://pantheon.io/blog/update-your-nginx-config-drupal-8
   rewrite ^/core/authorize.php/core/authorize.php(.*)$ /core/authorize.php$1;

   location ~ \..*/.*\.php$ {
      return 403;

   location ~ ^/sites/.*/private/ {
      return 403;

   # Block access to scripts in site files directory
   location ~ ^/sites/[^/]+/files/.*\.php$ {
      deny all;

   # Allow "Well-Known URIs" as per RFC 5785
   location ~* ^/.well-known/ {
      allow all;

   # Block access to "hidden" files and directories whose names begin with a
   # period. This includes directories used by version control systems such
   # as Subversion or Git to store control files.
   location ~ (^|/)\. {
      return 403;

   location / {
      # try_files $uri @rewrite; # For Drupal <= 6
      try_files $uri /index.php?$query_string; # For Drupal >= 7

   location @rewrite {
      rewrite ^/(.*)$ /index.php?q=$1;

   # Don't allow direct access to PHP files in the vendor directory.
   location ~ /vendor/.*\.php$ {
      deny all;
      return 404;

   # Protect files and directories from prying eyes.
   location ~* \.(engine|inc|install|make|module|profile|po|sh|.*sql|theme|twig|tpl(\.php)?|xtmpl|yml)(~|\.sw[op]|\.bak|\.orig|\.save)?$|composer\.(lock|json)$|web\.config$|^(\.(?!well-known).*|Entries.*|Repository|Root|Tag|Template)$|^#.*#$|\.php(~|\.sw[op]|\.bak|\.orig|\.save)$ {
      deny all;
      return 404;

   # In Drupal 8, we must also match new paths where the '.php' appears in
   # the middle, such as update.php/selection. The rule we use is strict,
   # and only allows this pattern with the update.php front controller.
   # This allows legacy path aliases in the form of
   # blog/index.php/legacy-path to continue to route to Drupal nodes. If
   # you do not have any paths like that, then you might prefer to use a
   # laxer rule, such as:
   # location ~ \.php(/|$) {
   # The laxer rule will continue to work if Drupal uses this new URL
   # pattern with front controllers other than update.php in a future
   # release.
   location ~ '\.php$|^/update.php' {
      fastcgi_split_path_info ^(.+?\.php)(|/.*)$;
      # Ensure the php file exists. Mitigates CVE-2019-11043
      try_files $fastcgi_script_name =404;
      # Security note: If you're running a version of PHP older than the
      # latest 5.3, you should have "cgi.fix_pathinfo = 0;" in php.ini.
      # See http://serverfault.com/q/627903/94922 for details.
      include fastcgi_params;
      # Block httpoxy attacks. See https://httpoxy.org/.
      fastcgi_param HTTP_PROXY "";
      fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
      fastcgi_param PATH_INFO $fastcgi_path_info;
      fastcgi_param QUERY_STRING $query_string;
      fastcgi_intercept_errors on;
      # PHP 5 socket location.
      #fastcgi_pass unix:/var/run/php5-fpm.sock;
      # PHP 7 socket location.
      #fastcgi_pass unix:/var/run/php/php7.0-fpm.sock;

   location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
      try_files $uri @rewrite;
      expires max;
      log_not_found off;

   # Fighting with Styles? This little gem is amazing.
   # location ~ ^/sites/.*/files/imagecache/ { # For Drupal <= 6
   location ~ ^/sites/.*/files/styles/ { # For Drupal >= 7
      try_files $uri @rewrite;

   # Handle private files through Drupal. Private file's path can come
   # with a language prefix.
   location ~ ^(/[a-z\-]+)?/system/files/ { # For Drupal >= 7
      try_files $uri /index.php?$query_string;

   # Enforce clean URLs
   # Removes index.php from urls like www.example.com/index.php/my-page --> www.example.com/my-page
   # Could be done with 301 for permanent or other redirect codes.
   if ($request_uri ~* "^(.*/)index\.php/(.*)") {
      return 307 $1$2;



Share this article

Share on facebook
Share on twitter
Share on linkedin

Leave a Reply

Your email address will not be published. Required fields are marked *

Scroll to Top