A more secure Drupal [multisite] install

I love the Drupal CMS. One of my favorite features of Drupal is the ability to do a multisite install. Several sites can share one codebase. Updates are easily rolled out to every site simultaneously. Overall, it’s a wonderful idea. But I have some problems with the implementation…

The standard way to set up a multisite install is to point each of the domain names at the Drupal install folder and let Drupal sort out which domain each request is coming from. It does a good job, too. But this method introduces some complications. For example, any content uploaded to site a is accessible from site b. A user that visits http://site1.com/myimage.jpg will find the same image as she finds at http://site2.com/myimage.jpg. Websites can’t have domain specific .htaccess or robots.txt files either, which might hurt search engine optimization of individual sites.

An interesting side effect of this is if you want to install something in a subdirectory of your site, for example a WordPress blog at http://site1.com/blog, that exact same WordPress blog will exist in its full glory at http://site2.com/blog

Another, and perhaps more grave, problem is that all that stands between the interweb and your very own personal settings is an .htaccess file. Install scripts, includes, site configurations and database passwords are in web accessible directories, and that is never a good thing.

We’ll look at one solution to these problems.

Directory structure

We’ll start by creating a couple of directories. The exact location will depend on your hosting situation. Since we want to restrict access to Drupal, it’s best if these directories aren’t accessible from the internet. In my case, this means I’ll use my home directory. Anything you type will be relative to this base directory.

mkdir drupal
mkdir site1
mkdir site2

You should prob’ly replace “site1” and “site2” with something a little more creative. Like the domain name of the sites or something…

You’ll now have three folders in your base directory: drupal, site1 and site2.

Unzip the latest version of Drupal to the drupal directory. Normally, each of the domain names using this Drupal install would point to this directory. We don’t want that, however. This folder will not be accessible from the interweb.

We will point site1.com at the site1 folder, and site2.com at the site2. How exactly to do this depends on your server and your webhost. For example, my webhost has a page in their control panel where I can select the destination for each domain name…

Now we will set up drupal for multisite themes and modules.

mkdir drupal/sites/all/modules
mkdir drupal/sites/all/themes

mkdir drupal/sites/site1.com
mkdir drupal/sites/site1.com/files
mkdir drupal/sites/site1.com/modules
mkdir drupal/sites/site1.com/themes

mkdir drupal/sites/site2.com
mkdir drupal/sites/site2.com/files
mkdir drupal/sites/site2.com/modules
mkdir drupal/sites/site2.com/themes

mkdir site1/sites
mkdir site1/sites/site1.com
mkdir site2/sites
mkdir site2/sites/site2.com

Make sure the files directories are writable (Drupal will remind you about this later if you don’t do it now):

chmod ug+w drupal/sites/site1.com/files
chmod ug+w drupal/sites/site2.com/files

So far, we’ve got a standard drupal multisite installation. The difference here is that nobody can access the drupal directory from the internet.

Our directory structure should now look something like this:

Directory structure

Copy some important files to the site directories.

cp drupal/.htaccess site1/
cp drupal/robots.txt site1/
cp drupal/.htaccess site2/
cp drupal/robots.txt site2/

Add a settings.php file to each of the sites directories. If you don’t do this, your sites will all use the “default” settings.php, and you’ll be all sorts of confused.

cp drupal/sites/default/settings.php drupal/sites/site1.com/
cp drupal/sites/default/settings.php drupal/sites/site2.com/

A little magic

There are a few things in the Drupal install folder that site visitors will need to have access to. Some images and CSS files, mostly. Here’s where the symlinks come in. These are special files that act like an alias to other another directory. This allows us to save disk space and upkeep time with a central codebase. But each site thinks it has it’s own copy of the code.

We’ll create symlinks to the shared resources we want to expose on the web.

ln -s /path/to/drupal/misc/ site1/misc
ln -s /path/to/drupal/modules/ site1/modules
ln -s /path/to/drupal/themes/ site1/themes
ln -s /path/to/drupal/sites/all/ site1/sites/all

ln -s /path/to/drupal/misc/ site2/misc
ln -s /path/to/drupal/modules/ site2/modules
ln -s /path/to/drupal/themes/ site2/themes
ln -s /path/to/drupal/sites/all/ site2/sites/all

Add symlinks to the files directory, so that uploaded content will be accessible as well.

ln -s /path/to/drupal/sites/site1.com/files/ site1/sites/site1.com/files
ln -s /path/to/drupal/sites/site2.com/files/ site2/sites/site2.com/files

If you plan to install site specific themes or modules they need to be linked as well.

ln -s /path/to/drupal/sites/site1.com/modules/ site1/sites/site1.com/modules
ln -s /path/to/drupal/sites/site1.com/themes/ site1/sites/site1.com/themes

ln -s /path/to/drupal/sites/site2.com/modules/ site2/sites/site2.com/modules
ln -s /path/to/drupal/sites/site2.com/themes/ site2/sites/site2.com/themes

Directory structure with symbolic links added

This step is crucial. If your site doesn’t work, or your themes don’t show up, or anything else is goofy, this is where your problem is. Visit the directories containing symlinks and type ls -al. If the symlinks show up red and/or blinking, you have a broken symlink. Try again, or redo them with full paths (something like /home/user/drupal/...).

Again, if something breaks, recheck your symlinks.

PHP files

Now we need to be able to access Drupal’s PHP files from the internet (files like index.php and update.php). Since we want to change the working directory before executing Drupal’s PHP files, a simple symlink isn’t quite enough. Instead, we will use a PHP file that changes to the Drupal directory then includes the file we care about.

Create a new file in the site1 directory called index.php. Edit the contents of this file:

<?php
    chdir('/path/to/drupal/');
    include('./index.php');
?>

Now create a couple more almost exactly like those ones.

cron.php:

<?php
    chdir('/path/to/drupal/');
    include('./cron.php');
?>

update.php:

<?php
    chdir('/path/to/drupal/');
    include('./update.php');
?>

xmlrpc.php:

<?php
    chdir('/path/to/drupal/');
    include('./xmlrpc.php');
?>

and install.php (delete this one after the first run! you won’t need it anymore…):

<?php
    chdir('/path/to/drupal/');
    include('./install.php');
?>

Copy all of these wrapper files to the site2 directory as well.

cp site1/*.php site2/

The final result

Final file structure

You now have the basis for a more secure Drupal multisite install. Now files uploaded to one domain will only be accessible to that domain. Configuration and install files won’t be accessible to the bad guys so you can sleep better at night. Third party software installation is a cinch, since a subdirectory in the site1 folder won’t show up on site2.com.

This technique is not just for multisite installations. It can be used for a more secure single site Drupal install as well… Just ignore everything about site2 :-).

A few last touches

Set up the site1.com domain to point at the site1 folder, and site2.com to point at site2. If you can’t figure out how to do this with your web host company, drop me a line or leave a comment and I’ll try to help you out.

When you configure your sites remember to let Drupal know that your file system paths are sites/site2.com/files and sites/site2.com/files respectively.

When you add shared themes and modules, put them in drupal/sites/all/modules and themes. To install site-specific themes and modules, add them to drupal/sites/site1.com/modules or themes

Now go build your sites! Visit each site in a browser, and the magical Drupal setup process will begin. Isaac Bowman has a pretty good guide for setting up Drupal in about 10 minutes.