Aug 102012
 

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:

[user@workstation workspace]$ mkdir myproject; echo 'Hello, world.' > myproject/index.php

Guess what? It’s already working!

hello world

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 ~]# echo "address=/dev/127.0.0.1" > /etc/dnsmasq.d/devtld.conf
[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:

[user@workstation workspace]$ ping asdf.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.

  5 Responses to “How to set up dynamic virtual hosts for web development”

  1. 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

  2. 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:

    
        VirtualDocumentRoot "/var/www/%1/public"
        ServerName vhosts.dev
        ServerAlias *.dev
        UseCanonicalName Off
    
    <Directory "/var/www/*">
        Options Indexes FollowSymLinks MultiViews
        AllowOverride All
        Order allow,deny
        Allow from all
    </Directory>
    

    Enjoy!

  3. 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:

    
    127.0.0.1 dev
    127.0.0.1 tmp.dev
    127.0.0.1 zf2.dev
    
    #You need to do this for every sub site since windows can't handle *.dev.
    

    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 external devices
    #Change %current-project% to whatever you are working on for external devices on your network
    <VirtualHost 10.0.0.10:80>
        ServerName 10.0.0.10
        VirtualDocumentRoot D:/xamp/htdocs/%current-project%/public_html
    </VirtualHost>
    

    Setup apache Dynamic Virtual Hosts for local pc

    
    #Setup apache Dynamic Virtual Hosts for local pc
    #%-2+ strips out the .dev at the end
    <VirtualHost *:80>
        VirtualDocumentRoot D:/xamp/htdocs/%-2+/public_html
    </VirtualHost>
    
    
    # Allow access to the virtual hosts
    <Directory "/xamp/htdocs/*/public_html">
        Options Indexes FollowSymLinks
        AllowOverride All
        Require all granted
    </Directory>
    

    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.)

  4. I have my vhosts as:

    /vhosts/[first letter of domain name]/[domain name (no subdomain)]/[domain name including subdomain]/htdocs

    Example: /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/htdocs

    Now, 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…

  5. Hi Evan, I just made a version of your post to work with symfony2 http://tmblr.co/ZYaYgtfnW5W1

 Leave a Reply

(required)

(required)

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre user="" computer="" color="" escaped="">