Using BIND locally on OS X for easy access to subdomains

With the advent of plugins like subdomain-fu, it has become quite easy to develop web applications that use subdomains. For the latest project I’m working on at Intridea, I need to allow clients to custom brand a web application with their company’s look and feel; I chose to use subdomains to achieve this goal.

In a staging or production environment, this involves modifying the DNS and adding a wildcard entry that points all hosts to the primary domain. For example, most hosted DNS management systems allow for creating new entries via some sort of GUI as seen here at the name.com DNS management screen:

In this case simply add “*” in the Record Host column, and the server’s IP in the Record Answer column. Then with a slight modification to the VirtualHost:

ServerAlias *.appname.com 

you’re usually in business.

To develop and test the subdomain aspect of a web application like this, developers usually edit their hosts file locally and add an entry that piggybacks on the localhost entry. On Mac OS X the hosts file is found at “/etc/hosts” and the setup I described might look something like this:

127.0.0.1       localhost appname.local client1.appname.local 

While this works for sites where there are only a few pre-defined subdomains, it is less desirable for situations where the application itself has the ability to introduce a new subdomain dynamically. When you want the app to be able to respond to any subdomain in development, it’s best to setup a local DNS server that will be able to handle a request to any subdomain without the need to continually modify configuration files.

To do this locally on OS X I’ll show you how to use the built in DNS server BIND. BIND should also be included with most flavors of Linux so this guide may work there as well, but there may be slight differences in the configuration files which could create small inconsistencies. Off hand, I know that OS X/Unix is pickier about tabs and spaces in it’s configuration files whereas Linux, for the most part, will accept any formatting. Let us know in the comments if you are able to get this setup working in a Linux environment!

A few notes on the setup instructions:

You’ll be editing files at the command line so make sure you are somewhat comfortable with Terminal before proceeding. As always, I recommend that you make backups of any configuration files you’ll be editing so that you can reference or revert to them if necessary without digging around on another mac or the web to find a suitable restore file. You can backup any file by using the cp command and appending .bak (or similar) to the end of the file like so:

$ sudo cp /etc/named.conf /etc/named.conf.bak 

You’ll also need to have root permissions to edit the files referenced in this guide so to elevate your terminal session issue the following command (the rest of the guide will assume elevated root permissions):

$ sudo -s 

BIND is disabled by default on OS X, but it’s easy to configure and turn on. Follow these steps to a happier development environment.

  1. Create the “rndc” configuration file by issuing the following command:
    $ rndc-confgen > /etc/rndc.conf 
  2. Create a keyfile that will allow the rndc client to talk to the name server and control it.
    $ head -n5 /etc/rndc.conf |tail -n4 > /etc/rndc.key 
  3. Edit the /etc/named.conf file to add a zone for the application. The zone can be named whatever you want, I’ll call it “appname.local”, it looks like this:
    zone "appname.local" IN {   type master;   file "appname.zone"; }; 

    and I placed it between the “localhost” zone and the “0.0.127.in-addr.arpa” zone in my named.conf file.

  4. To make sure the named.conf file is setup properly use the built in tool checkconf:
    $ named-checkconf /etc/named.conf 

    If this returns nothing then the named conf file is syntactically correct.

  5. Create the zone file in “/var/named/appname.zone”. It should look like this (make sure you have the spaces/tabs the same!):
    appname.local. 7200    IN       SOA     appname.local. root.appname.local. (           20100601 ;    Serial (a date in this case)           15      ; Refresh every 15 minutes             3600    ; Retry every hour             3000000 ; Expire after a month+             86400 ) ; Minimum ttl of 1 day               IN      NS      appname.local.               IN      A       127.0.0.1 *.appname.local.        IN      A       127.0.0.1 
  6. To make sure the zone file is setup properly use checkzone:
    $ named-checkzone /var/named/appname.zone 

    It should return something like:

    zone appname.local/IN: loaded serial 20100601 OK 
  7. After the zone file is setup you need to tell BIND to start when the machine boots. Issue the following command:
    $ launchctl load -w /System/Library/LaunchDaemons/org.isc.named.plist 
  8. Set up your machine’s network adapter’s DNS to look locally so BIND will resolve first. On Mac OS X go to System Preferences and edit each network adapter that you use, modifying the DNS settings to have “127.0.0.1” as the DNS server.
  9. Finally startup BIND and Flush the DNS Cache:
    $ sudo /usr/sbin/named $ sudo dscacheutil -flushcache 

    If the above commands don’t seem to work then try rebooting your machine to make sure BIND is properly started.

Note: If you’re using passenger locally on OS X you’ll also need to modify your VirtualHost file to have an alias. I’m using the passenger preference pane which makes this dead simple:

You can also edit the VirtualHost file directly and specify the ServerAlias if necessary.

You should now be able to visit any subdomain of your local app and have it resolve!