Skip to main content

Web Development Setup on OS-X

Here is a simple way to configure an OS-X system for web development. It allows one to develop multiple sites locally, without reconfiguring anything when changing sites/customers.

There are two key components to the strategy: dnsmasq and the httpd daemon's configuration.


dnsmasq will be used to handle domain name resolution for the sites under development. From the dnsmasq site:

Dnsmasq provides network infrastructure for small networks: DNS,
DHCP, router advertisement and network boot. It is designed to
be lightweight and have a small footprint, suitable for resource
constrained routers and firewalls. It has also been widely used
for tethering on smartphones and portable hotspots, and to
support virtual networking in virtualisation frameworks.

I configure dnsmasq so that any domain name ending in .dev resolves to my local machine. For example, will resolve to the IP address of the client's site, which will resolve to my local IP. I find that this arrangement make it quite simple to develop code locally and then deploy remotely.

Here are the steps:

  • Use macports to install dnsmasq. At the end of the installation, macports will emit a message about how to configure dnsmasq so that it is started automatically at boot time. Make a note of this for later use.

  • Configure dnsmasq so that any TLD ending in .dev TLD resolves to the local machine at

    • Copy the example dnsmasq.conf configuration provided by macports to the correct location:

      $ sudo cp `port contents dnsmasq | fgrep dnsmasq.conf` \
    • Update the configuration file to resolve .dev as described:

      $ sudo -s -- "echo 'address=/dev/' >> \
    • Use macports to configure launchd to start dnsmasq at boot time:

      $ sudo port load dnsmasq
    • Test the configuration of your local DNS server (i.e., dnsmasq):

      # Should resolve to
      $ dig @
      # Should both resolve to correct IP
      $ dig @
      $ dig

At this point, dnsmasq is working properly.

Configure DNS on OS-X

We now need to tweak OS-X so that it uses dnsmasq for name resolution. These instructions are valid for OS-X 10.11.6.

Use the 'System Preferences' application to modify the network settings:

  • Click Advanced on the network settings page. Click the DNS tab. I use Google's public DNS servers, I have two entries already: and You may have a different number of entries, including zero.

  • Under DNS Servers:, click the '+'. Type in This will put at the bottom of the list of DNS servers. However, we want (i.e., dnsmasq) to be consulted first, before any other servers. Drag up to the top of the list.

  • Click OK then Apply to save your changes.

  • After this, OS-X should send all DNS queries to dnsmasq first. If dnsmasq can't resolve the name, then it will move down the list. In my case, it will move onto and

    Note that OS-X does not use /etc/resolv.conf to choose DNS servers. Do not edit the file -- it is generated automatically when System Preferences changes related information. It exists so that software which does use resolv.conf will continue to function properly.

    Note also that there are other ways to configure dnsmasq. One alternate configuration directs OS-X to only query dnsmasq, thereby relying upon dnsmasq to query other servers as necessary.

    I think that the configuration presented here is the simplest. And it does work, so it's got that going for it.

  • Test your new configuration. 'dig' should resolve to and 'dig' should resolve to the site's correct address.

You say you want to undo what you've done? Not a problem:

  • In System Preferences, go to network, advanced, DNS, select, then click the - to remove the entry. Click OK then Apply. Once this has been done, OS-X will no longer attempt to use dnsmasq to resolve host names.

  • After that, you'll want to do the following:

    $ sudo launchctl stop org.macports.dnsmasq
    $ sudo rm /opt/local/etc/dnsmasq.conf
    $ sudo port uninstall dnsmasq
    $ sudo discoveryutil mdnsflushcache

Configuring Apache

dnsmasq was configured with a scheme that can be used for any development site you work with -- there are no configuration changes when you add or remove sites. Let's configure Apache in the same manner.


We will create a new directory which will contain all sites. Under this directory there will be additional directories -- one for each site. These subdirectories can be added/removed at will.

# First setup the container directory
$ sudo mkdir -p /www/sites

# Make it mine
$ ME=`whoami` sudo -E -s -- "chown -R $ME /www"

Let's setup two dummy sites for testing, one for customer and one for customer The wwwroot directory of each will contain the directory structure for all of the files to be served for that site.

$ mkdir -p /www/sites/foo/wwwroot
$ echo 'hello, world' > /www/sites/foo/wwwroot/index.html

$ mkdir -p /www/sites/bar/wwwroot
$ echo 'hello, world' > /www/sites/bar/wwwroot/index.html


To configure Apache, you'll need to edit two configuration files. This is for 10.11.6, YMMV.

File: /etc/apache2/httpd.conf

  1. Find the line that begins with #LoadModule and ends with Uncomment the line by removing the #. This will enable vhosts on your system.
  2. Find the line that begins with #Include and ends with httpd-vhosts.conf. Uncomment the line by removing the #. This tells Apache where to find the vhost configuration information.
  3. Save the changes.

File: /etc/apache2/extra/httpd-vhosts.conf

Comment out the configuration that is present and add the following:

<Directory "/www">
     Options Indexes MultiViews FollowSymLinks
     AllowOverride All
     Order allow,deny
     Allow from all
     Require all granted

<Virtualhost *:80>
     VirtualDocumentRoot "/www/home/wwwroot"
     UseCanonicalName Off

<Virtualhost *:80>
     VirtualDocumentRoot "/www/sites/%1/wwwroot"
     ServerAlias *.dev
     UseCanonicalName Off

Final Steps

Restart Apache so that it loads the new config: sudo apachectl restart.

Now, test your sites:

# Both should print 'hello, world'
$ curl
$ curl

At this point, you can add and remove sites under /www with impunity, never having to reconfigure or restart anything again.

Django Addendum

After writing the preceding, I wanted to do a similar thing, except running a Django WSGI application from a local repository while supporting SSL. This addendum covers that scenario.

The approach I took was to create a separate VHost configuration for the client. I simply included this configuration from httpd.conf. Here are the salient parts. Note that some of this content veers off into Django/WSGI content.

I created a httpd_macOS.conf file, which contained:

# Configuration for client's Django/WSGI application to be run locally on OS-X.
# Assumes:
#     * SSL not otherwise in use locally. I.e., /etc/apache2/extra/httpd-ssl.conf
#       has not been Included via httpd.conf.
#     * You are running dnsmasq locally and configured so that any any DNS name
#       ending in '.dev' resolves to
#     * 'mod_wsgi' was installed via 'pip install mod_wsgi'.
# To use this:
#   1) In /etc/apache2/httpd.conf, uncomment the following lines:
#         '#LoadModule socache_shmcb_module libexec/apache2/'
#         '#LoadModule ssl_module libexec/apache2/'
#   2) In /etc/apache2/httpd.conf, append a line with an Include directive
#      to Include this file.
#      'Include /Users/khe/repos/clientname/ecommerce/apache/httpd_macOS.conf'

# Next two lines are the output from 'mod_wsgi-express module-config'
LoadModule wsgi_module "/Users/khe/.virtualenvs/ecom/lib/python2.7/site-packages/mod_wsgi/server/"
WSGIPythonHome "/Users/khe/.virtualenvs/ecom"

# Grant httpd access to application html/etc. files.
<Directory "/Users/khe/repos/clientname/ecommerce/ecom/store">
    Options All
    AllowOverride All
    Order allow,deny
    Allow from all
    Require all granted

Alias /static                /Users/khe/repos/clientname/ecommerce/ecom/store/static-collect
Alias /static-collect/admin  /Users/khe/repos/clientname/ecommerce/ecom/store/static-collect/admin
Alias /media                 /Users/khe/repos/clientname/ecommerce/ecom/store/media

# Grant httpd access to WSGI script.
<Directory "/Users/khe/repos/clientname/ecommerce/apache">
   <Files "django.wsgi">
      Order allow,deny
      Allow from all
      Satisfy any

# LogLevel info    # helpful for debugging WSGI problems

<VirtualHost *:80>
    VirtualDocumentRoot "/Users/khe/repos/clientname/ecommerce/ecom/store"
    UseCanonicalName Off
    WSGIScriptAlias  /  /Users/khe/repos/clientname/ecommerce/apache/django.wsgi

# These are OK for local/dev use.  Do not use these for production.
Listen 443
SSLPassPhraseDialog builtin
SSLSessionCache         "shmcb:/private/var/run/ssl_scache(512000)"
SSLSessionCacheTimeout  300
SSLStaplingCache        "shmcb:/private/var/run/ssl_stapling(32768)
SSLStaplingStandardCacheTimeout 3600
SSLUseStapling On

<VirtualHost *:443>
    VirtualDocumentRoot "/Users/khe/repos/clientname/ecommerce/ecom/store"
    UseCanonicalName Off
    WSGIScriptAlias  /  /Users/khe/repos/clientname/ecommerce/apache/django.wsgi
    SSLEngine on
    # Create your own credentials and refer to them here.
    SSLCertificateFile     /Users/khe/repos/clientname/ecommerce/apache/ssl/
    SSLCertificateKeyFile  /Users/khe/repos/clientname/ecommerce/apache/ssl/

Note that instead of using /www as before, the above allows httpd to access files in my local directory structure, specifically the repo for a client. This is acceptable for local isolated development, but one should never do this on a server that is publicly accessible, as the security risk is large.

Since I was asked about it, here is a sample WSGI application (i.e., django.wsgi) that could be used with the above.

import os
import sys

# Update sys.path to include the Django application.  Here is
# how to do it relative to the location of the WSGI script. YMMV.
wsgi_dir = os.path.dirname(__file__)
app_dir = os.path.dirname(wsgi_dir)
sys.path.append(os.path.join(app_dir, 'ecom'))
sys.path.append(os.path.join(app_dir, 'ecom', 'store'))

os.environ['DJANGO_SETTINGS_MODULE'] = 'store.settings'

import django.core.handlers.wsgi
application = django.core.handlers.wsgi.WSGIHandler()