When I first started using a web server on my local machine for development, I used to create a separate virtual host for every site I worked on. Then I learned that you can use a ServerAlias with a wildcard to handle multiple sites with a single virtual host. Since then, I’ve been using the following simple configuration to test most of my projects:
ServerName tmp.code.dev ServerAlias *.code.dev VirtualDocumentRoot /path/to/projects/%1 AllowOverride All
This tells Apache that any domain ending with .code.dev
should be served by the directory in /path/to/projects/
that has the same name as the beginning of the domain. For example, foo.code.dev
would be served from /path/to/projects/foo/
. This reduces my per-project setup to just adding an entry in /etc/hosts
to map the custom domain to my local machine, e.g., 127.0.0.1 foo.code.dev
. Unfortunately, wildcards can’t be used in the hosts file, so this step needs to occur for every project. That’s a little annoying, but it’s easy enough that I was willing to live with it.
As I started working on more sites (as opposed to libraries or frameworks that happen to need a web server), I created another VirtualHost to create nicer domains and handle subdomains. If I’m working on foo.com
, I create a directory named /foo.com/
in my projects directory and use the domain local-dev.foo.com
for testing.
ServerName local-dev ServerAlias local-dev.* VirtualDocumentRoot /path/to/projects/%2+ AllowOverride All
Using %2+
tells Apache to use every part of the domain after the first part. You can read more about how the VirtualDocumentRoot
interpolation works in the mode_vhost_alias
documentation. And of course, I would add 127.0.0.1 local-dev.foo.com
to my hosts file.
Testing from physical devices
Unfortunately, there is a big limitation to this setup. When testing from a physical device, such as a phone or tablet, the custom domain won’t map to your local web server. Since the physical device doesn’t know anything about the hosts file on the machine with the web server, trying to load foo.code.dev
from the device’s browser isn’t going to work. This generally discouraged me from testing on physical devices because I never had a great solution to this problem. Whenever the need would arise, I’d create another virtual host using my machine’s LAN IP and a random port:
ServerName local-dev.foo.com DocumentRoot /path/to/projects/foo.com AllowOverride All
Then I can connect to 192.168.1.2:1234
instead of local-dev.foo.com
from any physical device connected to my network. But I’ve lost the benefit of the wildcard aliasing and now I need to remember which port to use. This can be quite burdensome as the number of sites increases, especially since each site needs to use a different port.
After years of dealing with this, I decided to ask for help on Twitter. I got a few responses, but the one I like the best came from Andrew Sheppard, who pointed me to xip.io.
xip.io to the rescue
xip.io is a free service from 37signals, which provides wildcard DNS for any IP address. Just like we can map *.code.dev
to the appropriate directory on our local machine, xip.io can map *.xip.io
to the appropriate IP for local testing. The URL structure is nice and simple, just prepend the IP address you want the domain to resolve to; the xip.io DNS server handles the rest. In this case, we’d use 192.168.1.2.xip.io
to map the domain to 192.168.1.2
.
This solves the DNS issue and removes the need to edit the hosts file for each site. But given a domain of 192.168.1.2.xip.io
, we still don’t know which directory to server the site from. To address this, xip.io allows adding as many subdomains as you want. So we can use foo.com.192.168.1.2.xip.io
and then create a virtual host that knows how to handle xip.io domains.
ServerName xip ServerAlias *.xip.io VirtualDocumentRoot /path/to/projects/%-7+ AllowOverride All
Handling DNS on your local machine
Although the xip.io setup does allow for zero-configuration setup, there are a few caveats.
The URLs are long and a bit ugly. I’ve created a simple page, bit.ly/xipio, to generate the xip.io URLs so I don’t need to type the full URL in the tiny address bar on my devices. I also kept my local-dev.*
virtual host enabled so that I can use shorter, nicer URLs if I want, though this does require adding an entry in the hosts file as mentioned above, and doesn’t allow testing from physical devices.
The URLs don’t work offline. Obviously if you can’t can’t connect to xip.io, the whole thing is going to fall apart.
The URLs aren’t shareable. Since the domain contains your personal LAN IP, sharing links with other developers working on the same site from their local machine won’t work (unless you happen to have the same LAN IP). However, it’s quite easy for the other person to change the IP after you’ve shared it with them.
More on link sharing
Link sharing is really useful when working on a project with other people, so it’s worth discussing specifically. As I mentioned above, while xip.io URLs can’t just be copy/pasted, they’re fairly easy to work with. However, having a common setup, such as local-dev.*
for your projects makes link sharing much easier. If everyone working on the project is using local-dev.foo.com
when testing from the main machine, then link sharing just works.
To avoid having everyone edit their hosts file to get the domain mapping to work, another option is to actually create a DNS record for local-dev.foo.com
. By creating an A record that points local-dev.foo.com
to 127.0.0.1
, you can get back to a zero-configuration setup. However, you do have the same offline problem as using xip.io, but that can be solved via the hosts file for any developer that needs offline access to work.
Other solutions
As with most things related to software development, there’s more than one solution to this problem. Matt Surabian wrote an article about using Charles Proxy to test local sites with physical devices in response to my tweet. Using Charles Proxy has a different set of pros and cons, so I suggest reading Matt’s article and determining which solution works best for you.