Setting up Apache on OSX 10.12 (Sierra) for no-setup wildcard virtual hosts

I want to host a copy of all the websites on which I work, right on the computer where I do my coding. I don’t want to depend on a server on my LAN that won’t be there when I am working from out of the office. I don’t want to work on a remote server that requires a slow (S)FTP loop to try out every change. And I also don’t want to work entirely from the command-line on a remote server. So I set up my macbook with wildcard DNS that points any hostname *.test to localhost (127.0.0.1). If I am working on example.com’s website, I can use “example.test” as a hostname that points right back to my machine. Now I need to set up Apache to host example.test. But adding a virtual host config for every website on which I work is going to be extremely laborious. Luckily Apache supports no-config mass virtual hosts. All I’ll need to do to add a new website for example.test is create an “example/” directory in the right spot.

Apache includes cool “mass virtual hosting” features that will allow it to suss out the DocumentRoot from the request hostname. First, let’s create a directory where our virtual hosts will live:

mkdir ~/Documents/vhosts

I put mine in a folder inside my Documents folder. For my login, that’s /Users/haroldp/Documents/vhosts. But you can put it just about anywhere. I added the following to my /etc/apache2/httpd.conf:

<VirtualHost *:80>
VirtualDocumentRoot /Users/haroldp/Documents/vhosts/%-2/htdocs
AddType application/x-httpd-php .php
DirectoryIndex index.php index.html

<Directory /Users/haroldp/Documents/vhosts>
Require all granted
AllowOverride All
Options +FollowSymLinks
</Directory>
</VirtualHost>

First, note that /Users/haroldp/Documents/vhosts makes sense for me on my computer, but it’s going to be different for everyone, so you can’t just copy & paste. Note that I tacked an /htdocs directory onto the end of my vhosts VirtualDocumentRoot directive. This is not necessary at all, but I like to have directories associated with a website, but outside the webspace. You don’t have to do that if you don’t want. Note too that I setup PHP, because I’ll be using that. You may or may not want those directives.

Then I uncommented the mod_vhost_alias module to enable apache’s mass vhosting directives:

LoadModule vhost_alias_module libexec/apache2/mod_vhost_alias.so

While I was in there I told apache to Listen only on localhost:

Listen 127.0.0.1:80

Because I don’t want anyone else to be able to hit the webserver on my laptop. Just me.

Next I enabled the PHP module by uncommenting:

LoadModule php5_module libexec/apache2/libphp5.so

I set the ServerName so Apache won’t whine about it:

ServerName www.test

I set the ServerAdmin so I know whose fault it is when something doesn’t work:

ServerAdmin haroldp@internal.org

And finally, I changed the user that Apache runs as to my login:

User haroldp
Group staff

WARNING, HIGH VOLTAGE!! This is very dangerous. I’m setting up apache to do its work, including running PHP scripts as my own UID. This means that a naughty script could do anything on my machine that I could, including very bad stuff. I am doing this so things like WordPress will create files with my login instead of the web user, which avoids a lot of hassles, and makes upgrades much easier. I am not worried too much about the security implications because I am running my own code, and the server is only available on localhost. If you are already on the machine, there are easier ways to do bad things.

Ok, let’s check our work:

sudo apachectl configtest

Fix any errors and rerun until Apache starts without issue. Then simply:

sudo apachectl start

If that worked, you should get a (404) page if you go to http://127.0.0.1/ . But let’s try out our virtual hosting:

mkdir ~/Documents/vhosts/foo
mkdir ~/Documents/vhosts/foo/htdocs
cat '<?php phpinfo(); ?>' > ~/Documents/vhosts/foo/htdocs/index.php

You should get a phpinfo() page if you go to http://foo.test/ .

Nice.

Edit 2/13/2018:

This article previously used the “.dev” top level domain. However, Google has bought and deployed .dev. So .dev is dead and all references have been changed to .test.

Edit 9/16/2020:

WHAT YEAR IS IT? I got a new macbook running Catalina and this setup required two updates. First, the default PHP version is 7, so you will have to adjust that config. Second, when I tried to access my vhosts I got an error like: AH00035: access to / denied (filesystem path ‘/Users/haroldp/Documents/vhosts’) because search permissions are missing on a component of the path”. I fought that for a while thinking that file permsiions had changed, but it fact it was a system setting. I needed to allow apache full access to the hard drive. Details here.

Edit: 1/30/2024:

I got a new macbook running Sonora. First you need to install PHP, as it’s no longer included in the system. I installed via Homebrew. Next I got an error starting Apache because the PHP module wasn’t signed. I followed this tutorial to sign it.

Using dnsmasq on OSX 10.12 (Sierra) for local dev domain wildcards

We want to develop websites or other internet services on our OSX computer. It’s convenient to point wildcard DNS for a whole (imaginary) top level domain to localhost, so we can invent as many domains as we want without having to edit any config files or do any work.

I used Homebrew to install dnsmasq:

% brew install dnsmasq

And then set up the config file:

cp /usr/local/opt/dnsmasq/dnsmasq.conf.example /usr/local/etc/dnsmasq.conf
vi /usr/local/etc/dnsmasq.conf

To the end of that file I simply added:

address=/.test/127.0.0.1

Which tells dnsmasq to resolve any hostname ending in .test to 127.0.0.1 (localhost).

Now we need to tell the OS to start dnsmasq automatically. Again, brew will do all the hard work for us:

sudo brew services start dnsmasq

Let’s test to see if it’s working:

dig foo.test @127.0.0.1
; <<>> DiG 9.8.3-P1 <<>> foo.test @127.0.0.1
;; global options: +cmd
;; Got answer:
;; ->>HEADER< ;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
 
;; QUESTION SECTION:
;foo.test. IN A
 
;; ANSWER SECTION:
foo.test. 0 IN A 127.0.0.1

We asked the resolver running @127.0.0.1 (dnsmasq) the address for “foo.test” and it returned “127.0.0.1” which is just what we want.

Now we need to get OSX to use that resolver for DNS lookups on the .test TLD. OSX makes this pretty easy:

sudo mkdir -p /etc/resolver
sudo vi /etc/resolver/test

And insert a line in that file just like you might in /etc/resolv.conf:

nameserver 127.0.0.1

That’s it. Anything ending with .test will point to localhost. So our next step is to run a server there.

Edit 2/13/2018:

This article previously used the “.dev” top level domain. However, Google has bought and deployed .dev. So .dev is dead and all references have been changed to .test.