Drupal (or any CMS) and multiple RewriteBase entries

Abstract

There is a general frustration with the lack of flexibility in Apache handling different URL bases. I present here a method which might be old hat to some, but if so, I have never seen it documented anywhere before. It works with Drupal in this example, or any CMS possibly with a spot of modification.

There is a common scenario with Drupal, where you want one installation to serve multiple domains. There is a system in place for this, but Apache is the complicating factor. You want to have what looks like multiple RewriteBase entries, one for each domain. The problem is that RewriteCond does not apply to RewriteBase. There are long threads on this issue on the Drupal forums, but I could not find a solution until I cooked up this one. I like it so much I will use it on all my sites from now, with or without Drupal.

What does RewriteBase do? It chops off the base from the URL, the whacks it back on at the end, so to your whole setup it looks like the base URL is shorter than it really is. A RewriteRule can simulate one half of that: it can redirect pages to the same script at a different URL depending on the domain. The other half, unfortunately, it cannot do. But, a PHP script in fact can. That is then the key idea.

I will illustrate with a worked example showing the flexibility. We have one site, accessed at three different domains: the primary one, which does not support HTTPS, as a subdirectory of the secondary one which does have HTTPS (both run from the same server), and from localhost on my desktop. I develop the site using a git clone, and check any changes before pushing them to the live site. The clone has to have exactly the same files as the live site—the same .htaccess, and the same configuration files, or else the syncing gets out of hand with the multiple developers.

Drupal has a rather funky set-up where multiple sites and databases can be used with one install of the system. My solution does also extend easily to the case where the different domains are actually serving up different sites, though I don’t use it myself.

This is how we do it. Assume that the site is installed in the drupal directory, and that the all the configuration is correct (that is, it works correctly on one of the domains you want to use).

Example 1. The relevant section of .htaccess
<…>

# The story is that the site might be accessed from three different
# places: domain.com,    the standard address for users
#         secdomain.com, for when SSL is required (all logins and admin stuff)
#         localhost,     for development clones

# We start by making sure that secdomain uses only SSL:
RewriteCond %{HTTP_HOST} secdomain\.com$ [NC]
RewriteCond %{HTTPS} =off
RewriteRule ^(.*)$ https://www.secdomain.com%{REQUEST_URI} [L]
# Also neaten the domain.com domain
RewriteCond %{HTTP_HOST} ^domain\.com$ [NC]
RewriteRule ^ http://www.%{HTTP_HOST}%{REQUEST_URI} [L,R=301]

# We use a hacked version of index.php to simulate multiple RewriteBase
# directives (which Apache cannot handle). Depending on the site, we will
# redirect everything to index page, and trim it as appropriate there.
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{HTTP_HOST} domain\.com$ [NC]
RewriteCond %{REQUEST_URI} !=/favicon.ico
RewriteRule ^(.*)$ /drupal/index-fake.php?q=%{REQUEST_URI} [L,QSA]

RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{HTTP_HOST} secdomain\.com$ [NC]
RewriteCond %{REQUEST_URI} !=/favicon.ico
RewriteRule ^(.*)$ /<subdir1>/drupal/index-fake.php?q=%{REQUEST_URI} [L,QSA]

# If not match so far, then guess! In this case, it is localhost
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} !=/favicon.ico
#Note that subdir2 must have same last component as subdir1 above
RewriteRule ^.*$ /<subdir2>/drupal/index-fake.php?q=%{REQUEST_URI} [L,QSA]

<…>
Example 2. index-fake.php
<?php
/**
 * @file
 * This is the redirection file — simulate the last portion of RewriteBase
 * by chopping off the base from the start of any URL.
 */

$crop      = pathinfo(realpath('..'), PATHINFO_BASENAME);
$_GET['q'] = preg_replace("#^(/$crop)?/drupal#", '', $_GET['q']);
require_once 'index.php';

That’s it! The magic is in index-fake.php, where we assume that the subdirectories on the various domains all end in the same component and chop off the URL up to there. Multiple RewriteBase entries are simulated, and the Drupal install’s won’t know that the index.php file wasn’t called directly.

(As a final note, I connect to the database on a different port based on the hostname in the settings file, so that the same database powers the content on the live and dev versions of the site.)