Running Drupal on a Windows Server 2008 machine

We recently struggled through figuring out how to make Drupal run on a Windows Server 2008 machine that was running IIS. Every Drupal site that we had dealt with prior to this was hosted on a Linux machine running Apache, MySQL and PHP, and so we needed to figure out what could be done differently to make it work on our in-house server, which was already running a production instance of ArcGIS Server under IIS7.

From the outset, we had two options:

  1. Figure out how to make PHP run natively under IIS. Apparently there's been a lot of work done to this end, and IIS has come a long way. In fact, through Microsoft's Web Platform, you can do a "one-click" install of Acquia Drupal under IIS. This installs extensions to IIS that are needed to run PHP natively, sets up MySQL and configures you with a vanilla Acquia Drupal installation.
  2. Install Apache, make it listen to some port other than 80 (which IIS was tying up), and forward specific traffic hitting IIS to our Apache server. Often people do this kind of thing, only in reverse -- Setup Apache as the "front" web server that receives all port 80 traffic, and forward some of it to IIS for the appropriate applications. In our case, though, we wanted IIS to remain in front because keeping the ArcGIS Server instance up was the top priority.
The first thing I tried was the Web Platform installer for Acquia Drupal, and it wasn't exactly "one-click", though I think it worked for the most part. The installation didn't complete successfully the very first time, though it did the second, and the creation of the Drupal database in MySQL didn't work (I think that was actually because I tried to run the install.php on Internet Explorer...) Even so, we weren't convinced that PHP natively running under IIS wouldn't cause us problems later down the road. Would all the modules we want to use work? Is Drupal really that good at separating itself from its web server platform? These were unknowns and we were cowardly. We've seen it work wonderfully under Apache before, and so we were inclined to go the second route. Below is a rough walkthrough of what we did and how it works.

Setting up a reverse proxy on IIS7 to Apache on the same machine

  1. The first thing we had to do was install two IIS extensions: Application Request Routing and URL Rewrite. These can be downloaded and installed via the snazzy Microsoft Web Platform Installer.
  2. Next we installed XAMPP for Windows and configured Apache to listen only on port 8888. This is done by simply editing the "Listen" directive in the {xampp root}\apache\conf\httpd.conf file.
  3. We spun up a DNS entry for testing purposes that forwarded web traffic for http://repository.azgs.usgin.org to our webserver. In IIS we setup a new website with a binding to listen to traffic on any IP, port 80 for repository.azgs.usgin.org. We named the website "Drupal Repository".
  4. Now that we installed the URL Rewrite extension, we see that there is a feature we can configure under our new "Drupal Repository" site called, predictably, "URL Rewrite". Here you'll see that you can setup Inbound and Outbound rewrite rules.
    1. Inbound rules affect incoming traffic. They look for a specific string in the requested URL, and if it finds that string, rewrites the URL to what you want it to be, sending the traffic to whatever address/port you'd like.
    2. Outbound rules affect outgoing documents. They actually look at the html pages the server constructed and look through tags you get to specify looking for URLs. Again, if a string is matched, the URL can be rewritten. For example, you could setup and outbound rule to read all HTML documents coming out of your server, and replace the href attribute of all A tags with http://this.will.never.work.com.
  5. URL rewrite inbound ruleWe set up an Inbound URL Rewrite rule by clicking Add a Rule and making a blank Inbound Rule, shown here to the right. The rule applies the regular expression in the "Pattern" field to everything after the "http://mydomain.com/" in a URL. Further down, an Action is defined. This rewrite action would change
    http://anydomain.com/something
    to
    http://repository.apache.localhost:8888/something
    or in my case, it will change
    http://repository.azgs.usgin.org/drupalHome
    to
    http://repository.apache.localhost:8888/drupalHome
    You'll need to experiment a little bit with this setup to understand exactly how the rewrite works, or in my case, to learn how to write regular expressions. The "Test Pattern" button is very helpful to that end.
  6. Once our inbound rule is set, we need to make sure that Apache will be listening to the URL we're directing the traffic to. There are two parts to this process.
    1. First, we need to adjust the Hosts file on the server. This file is found at {Windows Directory}\system32\drivers\etc\hosts. Note that you will need administrative privileges to adjust this file. What this file does it hard-wires a relationship between a domain name and an IP address into your computer. You'll probably see an entry in it for
      127.0.0.1    localhost
      What this does is tells your computer to send packets to 127.0.0.1 (itself) whenever a request is made against the localhost domain.

      We're going to add an entry for
      127.0.0.1    repository.apache.localhost
      Now whenever a request is made to repository.apache.localhost it will be sent along to 127.0.0.1, or the computer itself.
    2. Now that the repository.apache.localhost traffic is being sent to our machine, we need to make Apache pick it up. This is done through what Apace calls Virtual Hosts

      Take a look at {Apache root directory}\conf\httpd.conf. We've already messed with this file to make sure that Apache is listening to port 8888, now we're going to add some logic to have it do something specifically when traffic to port 8888 is also addressed as repository.apache.localhost.

      Find near the bottom of the document the lines that say
      ## Virtual hosts
      Include conf/extra/httpd-vhosts.conf
      Replace this block with the following
      ## Virtual hosts
      NameVirtualHost *:8888
      Include conf/extra/vhosts/localhost.conf
      Include conf/extra/vhosts/repository.azgs.usgin.org.conf
      The first line tells Apache to enable name-based virtual hosts on port 8888. The second and third lines point Apache to virtual hosts configuration files, each of which handles traffic to a specific domain name.

      First create a file at {your Apache root directory}\conf\extra\vhosts\localhost.conf and paste the following:
      <VirtualHost *:8888>
         DocumentRoot "/path/to/my/xampp/htdocs"
         ServerName localhost
         ErrorLog logs/xampp_error.log
         CustomLog logs/xampp_access.log combined
      </VirtualHost>
      
      This file will tell Apache to watch for traffic directed to localhost:8888 and send it along to the folder defined in the DocumentRoot. Putting this here will insure that XAMPP configuration pages are still accessible via localhost:8888

      Next, create a file at {your Apache root directory}\conf\extra\vhosts\repository.azgs.usgin.org.conf and paste this:
      <VirtualHost *:8888>
         DocumentRoot "/path/to/my/drupal/site"
         ServerName repository.azgs.usgin.org
         ServerAlias repository.apache.localhost
         ErrorLog /path/to/my/drupal/site/logs/error.log
         CustomLog /path/to/my/drupal/site/logs/access.log combined
         
         <Directory "/path/to/my/drupal/site">
      	   AllowOverride All
         </Directory>
      </VirtualHost>
      
      Here, notice that we are addressing both traffic to repository.azgs.usgin.org:8888 and repository.apache.localhost:8888 by using the ServerAlias directive. Traffic to either of these locations will be sent along happily to our Drupal directory.
    3. Now just restart Apache to make your changes take effect.
IIS to Apache Reverse Proxy

That will do it. This diagram is meant to help us see the "big picture" and understand how all the request routing works.

I mentioned outbound rules in the URL rewrite extension, but you may have noticed that we never used them. Outbound rules have a few "gotchas"... the major one being that outbound rules cannot be applied to pages that are compressed by Apache. Compressing some of Drupal's outputs yields very significant performance gains, speeding up the transfer of pages from the server to the client. If you turn on an outbound rule and have Drupal set to gzip its outputs, when you try and get to a page you'll see a 500 error from IIS. 

The upshot is, though, that Drupal handles almost all of its internal links as relative. As a result, you can get away with having no outbound rewrite rules if the Drupal site has its own domain or subdomain. This is what we did here - our Drupal site is accessed via http://repository.azgs.usgin.org. If we instead wanted our site to be accessed from http://usgin.org/azgsRepository, we would need to start implementing outbound rewrite rules. Again, we could handle this by making sure that Drupal doesn't gzip its documents, but that means a performance hit that we're not interested in taking.

File Uploading Problems (RJC 7/15/2010)

We ran into a situation with our setup in which we could not upload large files to our document repository. This site is a Drupal site that runs under the setup we've described here. We found that, although all our PHP memory and file-size parameters were set correctly, we still could not upload files larger than about 28MB.

It turns out that this was a limitation set in the Request Filtering Module in IIS7. This post helped me identify the problem, and this one described very nicely how to remedy the situation. Basically, you need to increase the "Maximum allowed content length". By default it is set to 30,000,000 bytes, which is about 28 Megabytes. We increased it to 100,000,000 bytes.