Skip to content

Nginx, Drupal 9 Multisite, and Path based URLs

My team and I ran into a a bit of a tricky situation this past week, and I wanted to document the solution for myself, and anyone else who might come across the same issue.

The Situation:

I had a Drupal 9 app on our dev server, configured as a multisite. It was configured as a host-based setup; ie site1.example.com, site2.example.com, and so on. I got the go-ahead to push the sites to the production server, but the client wanted a change. Instead of having URLs that were host-based, they wanted path-based URLS – example.com/site1/ instead of site1.example.com. “Sure, no problem”, I thought. It ended up being something of a problem, though. It turns out, plenty of people had used nginx to serve up entire sites contained in subdirectories, but I couldn’t find a single working example of anyone using a Drupal multisite in this way. There were some old Stack Overflow questions, some Drupal documentation for Drupal 7, but nothing I could find that got me to a working solution. I dug through nginx documentation until my eyes burned, with no luck. Finally, a teammate and I went to the nginx source code and poured through, trying to figure out what was needed.

The Solution

It turns out, nginx doesn’t have a way to look for the php application anywhere but the specific directory as all the other files. The only way to get nginx to make use of the multisite codebase was to trick it using symlinks.

So, given the following structure:

/var/www/drupal
/var/www/drupal/sites/default
/var/www/drupal/sites/site1
/var/www/drupal/sites/site2

First, we need to create a symlink in /var/www/drupal for each of the non-default sites:

in /var/www/drupal
ln -s . site1
ln -s . site2

Then you need to edit the sites.php to reflect the paths properly

$sites['example.com'] = 'default';
$sites['example.com.site1'] = 'site1';
$sites['example.com.site2'] = 'site2';

Finally, you need to create a the config for the sites, either in sites-available or in conf.d/. Below is an example of a working config:

server {
  server_name example.com;
  root /var/www/drupal;
  index index.php;
  listen 443 ssl http2;

  ssl_certificate      /etc/nginx/certs/example.crt;
  ssl_certificate_key  /etc/nginx/certs/example.key;

  location / {
    try_files $uri /index.php?$query_string;
  }

  #location /site1/ {
  #   try_files $uri /site1/index.php?$query_string;
  #}

  #location /site2/ {
  #   try_files $uri /site2/index.php?$query_string;
  #}

  location ~ '\.php$|^/update.php' {
    # NOTE: You should have "cgi.fix_pathinfo = 0;" in php.ini
    fastcgi_split_path_info ^(.+?\.php)(|/.*)$;
    include /etc/nginx/fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    fastcgi_intercept_errors on;
    fastcgi_pass  php-fpm;
  }

  location @rewrite {
    rewrite ^/(.*)$ /index.php?q=$1;
  }
  location ~ ^/sites/.*/files/styles/ {
    try_files $uri @rewrite;
  }
  location ~* \.(js|css|png|jpg|jpeg|gif|ico)$ {
    expires max;
    log_not_found off;
    proxy_cache_bypass 1;
  }
}

This, with the symlinks, will have nginx serving up the main codebase in /var/www/drupal, but will still find the individual site files in sites/. Works like a charm!

Published inTechnical explanation

Be First to Comment

Leave a Reply

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