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:
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
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
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.