I’ve been meaning to blog about this topic for quite a while, but like most other I kept putting it off. However, a recent exchange on Twitter has shown there might actually be some demand for this, so here it is.
Over the years, I’ve slowly evolved my web development environment for efficiency and convenience. A couple of years ago, I realized that I was sick and tired of having to create a new virtual host and edit my host file for every single project, and decided to come up with a solution. Two years later, and I couldn’t imagine working without my awesome dynamic virtual host setup. It’s well-worth the few minutes you’ll invest getting it working.
So just how cool is it? Well, let’s take a look at how simple it is for me to start a new project:
Guess what? It’s already working!

Pretty cool, huh? So now that I’ve convinced you, let me show you how it’s done.
Step 1: Setting up the *.dev domain
The first step is to make *.dev resolve to 127.0.0.1. An easy way to do this is with dnsmasq. On Linux, simply install dnsmasq with your package manager (yum, apt-get, pacman, etc).
On Fedora (my operating system of choice as of writing this), simply run the following as root:
[root@workstation ~]# service dnsmasq start
[root@workstation ~]# chkconfig dnsmasq on
For other distributions, find your dnsmasq.conf and make sure it contains the following:
listen-address=127.0.0.1 address=/.dev/127.0.0.1
Also make sure that you set dnsmasq to start on boot. I’ve been told Ubuntu users may need to reboot to get dnsmasq working at this point.
Instructions for Mac OS X come from Rob Allen’s blog:
On OS X, use Homebrew to install like this: brew install dnsmasq. … Note that on OS X, you should set it to start up automatically using launchd as noted in the instructions after installation. You also need to copy the configuration file to /etc using: cp /usr/local/Cellar/dnsmasq/2.57/dnsmasq.conf.example /usr/local/etc/dnsmasq.conf (or whatever the latest version number is).
Finally, go into your network settings and add 127.0.0.1 as a DNS server. (I went a little crazy here and used a NetworkManager script to prefix my DNS server list with 127.0.0.1 so that I could still allow DHCP to assign my main DNS servers, however, this is by no means necessary.)
If all went well, you should now be able to ping anything.dev:
PING asdf.dev (127.0.0.1) 56(84) bytes of data.
64 bytes from localhost.localdomain (127.0.0.1): icmp_req=1 ttl=64 time=0.019 ms
64 bytes from localhost.localdomain (127.0.0.1): icmp_req=2 ttl=64 time=0.034 ms
64 bytes from localhost.localdomain (127.0.0.1): icmp_req=3 ttl=64 time=0.035 ms
Windows users, you’re on your own — this might get you going in the right direction. Leave a comment with instructions if you get *.dev working on Windows.
Step 2: Configuring the web server
Now that we have the wildcard .dev domain pointed to localhost, we need to configure the web server.
Nginx
server { listen 80; server_name .dev; # dynamic vhosts for development set $basepath "/path/to/your/workspace"; set $domain $host; if ($domain ~ "^(.*)\.dev$") { set $domain $1; } set $rootpath "${domain}"; if (-d $basepath/$domain/public) { set $rootpath "${domain}/public"; } if (-d $basepath/$domain/httpdocs) { set $rootpath "${domain}/httpdocs"; } if (-d $basepath/$domain/web) { set $rootpath "${domain}/web"; } if (-f $basepath/$domain/index.php) { set $rootpath $domain; } if (-f $basepath/$domain/index.html) { set $rootpath $domain; } root $basepath/$rootpath; # enable PHP index index.php app.php index.html; location / { index index.php; error_page 404 = @indexphp; } location @indexphp { rewrite ^(.*)$ /index.php$1; } location ~ ^(.+\.php)(?:/.+)?$ { expires off; fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_split_path_info ^(.+\.php)(/.*)$; include fastcgi_params; } # rewrite to index.php for pretty URL's try_files $uri $uri/ /index.php?$args; # block .ht* access location ~ /\.ht { deny all; } }
NOTE: This configuration is meant for non-sensitive development environments only. Generally speaking the if directive is evil in Nginx configs.
(TO-DO: Improve this to work with Symfony’s app|app_dev.php, and verify that it works with clean URLs for the major PHP frameworks/apps.)
Apache
I haven’t tested this Apache config, and it’s not quite as flexible as my Nginx counterpart (though I’m sure someone clever enough could figure out how to port the added Nginx logic to the Apache config), but this should get you fairly similar functionality on Apache:
<Virtualhost *:80> VirtualDocumentRoot "/path/to/your/workspace/%1/public" ServerName vhosts.dev ServerAlias *.dev UseCanonicalName Off <Directory "/path/to/your/workspace/*"> Options Indexes FollowSymLinks MultiViews AllowOverride All Order allow,deny Allow from all </Directory> </Virtualhost>
For more information about doing this with Apache, see Rob Allen’s Automatic Apache vhosts post.
Bonus: Make your vhosts public!
If you’ve come this far, you may also be interested in how I make my local development vhosts public from behind any connection.

Thanks for posting this. I’ve been trying to figure out how to configure dnamasq for a while now on my Ubuntu machine, and creating that file did the trick! On Windows, Acrylic works very well and is quite easy to set up. Acrylic is a lot simpler actually – configuration is very similar to the Windows hosts file.
Also, some food for thought… I build a lot of multi-tenant (multi-domain) applications, and rather than creating a lot of virtual hosts, I just create one per application (which isn’t a lot of work), and then using ServerAlias *.app.dev to catch all subdomains.
Cheers
First off, I love the article thank you so much! I wish to add though that if you try this with Ubuntu (12.04 as of writing this) you may have a few issues, here is how I got this to function in addition to the above steps:
In the file /etc/dnsmasq.conf:
Uncomment the line: “bind-interfaces”
Install the vhosts apache mod:
sudo a2enmod vhost_alias
Also instead of using the above apache conf for /etc/apache2/sites-enabled/000-default. I decided to create 001-dev in the folder and put this inside it:
Enjoy!
I don’t think you need Acrylic DNS Proxy for windows. All it does is caching DNS requests which chrome for example already does itself.
All I did was editing the hosts file and added:
You need to do this for every sub site since windows can’t handle *.dev.
Setup apache Dynamic Virtual Hosts for external devices
Setup apache Dynamic Virtual Hosts for local pc
And that’s it for windows. By the way, this is for apache 2.4.x. Some things might be different for other other versions.
(See the above examples as a Gist.)
I have my vhosts as:
/vhosts/[first letter of domain name]/[domain name (no subdomain)]/[domain name including subdomain]/htdocsExample: /vhosts/e/example.com/site1.example.com /vhosts/e/example.com/site2.example.com /vhosts/e/example.com/www.example.com
In normal apache-language that’s (there’s probably a better solution):
VirtualDocumentRoot /vhosts/%-2.1/%-2.0.%-1.0/%0/htdocsNow, this whole folder structure is an exact copy of the actual server, with domain names ending in .com or .org. Is it possible to make apache find the correct folders anyhow while keeping the folder structure intact? That is, while using .dev locally to access all these sites…
Hi Evan, I just made a version of your post to work with symfony2 http://tmblr.co/ZYaYgtfnW5W1