PHP
Article

Set up Automatic Virtual Hosts with Nginx and Apache

By Bruno Skvorc

When starting new applications, a developer often needs to set up a new virtual host. This includes setting up new configuration files for Apache or new Nginx site entries. Sometimes you need to do this manually. Other times, it’s handled automatically if you just define a site-to-folder mapping. But wouldn’t it be practical if we could skip that step, too, and just have any URL ending in, for example, .local.com look for its files automatically, so all we need to do is change our etc/hosts file only once ever? Whoa!

Mind blown

In this tutorial, we’ll set up Nginx and Apache to automatically look inside certain folders when it detects a given URL format. For example, the URL mynewsite.local.com will automatically look inside the DOC_ROOT/nynewsite/public folder for the index.php file. You will then be able to define other patterns suitable for generic scripts, standard MVC apps (public/index.php) and Symfony/Silex based apps (web/app.php) – all depending on the URL format (e.g. mynewsite.sf2local.com could look inside web/ for app.php, as is typical of those frameworks).

This tutorial will be Unix-centric, but is Windows-friendly if you use our Homestead Improved box.

Before we begin, make sure you have something.local.com in your /etc/hosts file, so that we can test this URL as we go along. If desired, modify it to your liking. I’ll be focusing on the local.com suffix for triggering Vhost lookups inside the something folder in our document root, but this can be literally anything that’s valid in a URL charset.

Setting up for Nginx

Nginx Logo

Assuming you have an Nginx setup running, either via Homestead Improved or any other means, let’s create a new entry in the folder Nginx typically uses to read site configurations from: sites-available. In a standard installation, Nginx will include everything from the folder sites-enabled in its configuration. Putting the file we’re about to write into sites-available will require us to activate it later.

vim /etc/nginx/sites-available/wildcard.conf

We first define a server block and tell it to listen on port 80:

server {
	listen 80;
}

Everything else we write will be put inside the server block, below the listen statement.

server_name ~^(www\.)?(?<sname>.+?).local.com$;

This right here is the most important part. It tells Nginx “The server name will come in the form of this regular expression. Pull out the sname bit, save it for later”.

root /home/vagrant/Code/$sname;

This tells Nginx “The root folder of this web application is in this location”. Since it appends sname from the above regex, the path becomes dynamic, corresponding to the URL. If you’re not running a virtual machine, or have a differently set up root folder in general, feel free to change this – just make sure it ends in $sname;. If your apps typically have their front controller in the public folder, feel free to append public so the root statement looks like this:

root /home/vagrant/Code/$sname/public;

This approach is what I’ll be using in the final version of the file.

The rest of the configuration can stay the same as usual. That’s more or less it, unless you would also like to have dynamic error logs, in which case the error log entries in the file should look like this:

access_log /var/log/nginx/$sname-access.log;
    error_log  /var/log/nginx/wildcard-error.log debug;

Note that we cannot have a variable in the error log, only the access log. Not sure why this was implemented like this. This does mean, unfortunately, that your wildcard error logs will be merged.

What follows is the full code of the configuration file we wrote. Depending on your installation, some other things may vary (like php-fpm lines etc.) but that’s outside the scope of this tutorial.

```
server {
    listen 80;
    server_name ~^(www\.)?(?<sname>.+?).local.com$;
    root /home/vagrant/Code/$sname/public;

    index index.html index.htm index.php;

    charset utf-8;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location = /favicon.ico { access_log off; log_not_found off; }
    location = /robots.txt  { access_log off; log_not_found off; }

    access_log /var/log/nginx/$sname-access.log;
    error_log  /var/log/nginx/wildcard-error.log debug;

    error_page 404 /index.php;

    sendfile off;

    location ~ \.php$ {
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass unix:/var/run/php5-fpm.sock;
        fastcgi_index index.php;
        include fastcgi_params;
    }

    location ~ /\.ht {
        deny all;
    }
}

It’s possible that your Nginx installation is set up differently, so you might need to look around for site configuration files. It’s also possible that all your Nginx configuration is in a single file, in which case just add the above configuration to it – that might also be a file called default in your sites-available folder. Doesn’t matter where this configuration is, as long as it’s loaded – see configuration auto-include locations by peeking inside etc/nginx/nginx.conf.

Finally, we need to enable the new configuration by creating a symlink in the sites-enabled folder:

sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/myapp
sudo service nginx restart

If you now visit something.local.com in your browser, the files from (in my case) vagrant/Code/something/public will be loaded.

Setting up for Apache

Apache Logo

In this section, I’ll assume you have a running Apache instance that serves your PHP sites as usual. This could be via PuPHPet or with your friendly neighborhood *AMP stack, irrelevant – as long as it works.

First, we need to make sure mod_vhost_alias is enabled. This is the Apache mod used to give us the functionality we need. There are two ways to do this. In more streamlined installations, the command a2enmod will be available in the terminal, and all it takes is sudo a2enmod vhost_alias. In others, again due to the curse of Linux, you’ll need to look inside the main configuration file, either /etc/apache2/apache2.conf or /etc/apache2/httpd.conf or the like. If this is your case, try and find a line containing mod_vhost_alias. If it’s not there, add the following line:

LoadModule vhost_alias_module modules/mod_vhost_alias.so

Note that the path at the end needs to match the location of Apache modules on your system. This also varies due to the curse of Linux, but is often in /etc/apache2/mods-available.

Just like Nginx, Apache usually loads its Vhost configurations from /etc/apache2/sites-available and by extension /etc/apache2/sites-enabled. It usually also comes with a module for easy enabling and disabling of sites, like so: sudo a2ensite mysite.conf. This is, for all intents and purposes, identical to the Nginx section above on creating a symlink to enable a new configuration. It loads these alphabetically, so we’ll make a new configuration file in sites-enabled (or do it in sites-available and enable the site subsequently) called 000-default.conf.

The first statement we put in there will be:

UseCanonicalName Off

This is required so that we can dynamically assign aliases to the URLs Apache receives. Then, we define the VirtualHost block:

<VirtualHost *:80>
	ServerName vhosts.fqdn
	ServerAlias *.local.com
	VirtualDocumentRoot /var/www/%1+
</VirtualHost>

The %1 means “Take the first part of the dot-separated domain name”. For other options, and if you wish to configure it differently, see the docs.

If we now put the folder something into /var/www and inside it index.php with some Hello World content, it should all work. But in this case, it’ll output the contents of the PHP file as text. We need to configure the rest of the VirtualHost’s parameters as usual. I’ll paste my PuPHPet version below – your configuration may vary.

UseCanonicalName Off
<VirtualHost *:80>
  ServerName vhosts.fqdn
  ServerAlias *.local.com

  VirtualDocumentRoot /var/www/%1

  <Directory ~ "/var/www/.*">
    Options Indexes FollowSymlinks MultiViews
    AllowOverride All
    Require all granted
    
    <FilesMatch "\.php$">
      Require all granted
      SetHandler proxy:fcgi://127.0.0.1:9000
      
    </FilesMatch>

  </Directory>
</VirtualHost>

Now, if you reload Apache configuration with sudo service apache2 restart or whichever command works on your distribution, you should be able to access something.local.com in your browser and once again see it working if you put some PHP files into var/www/something/public.

Optional: dnsmasq

Optionally, if you’re on Linux or OS X, you can install dnsmasq.

Dnsmasq is a tool which, among other things, forwards a range of URLs to a given IP. It’s like regex support for /etc/hosts. With this tool, you can set all URLs ending in .local.com or whatever else you choose to redirect to your virtual machine’s IP or to your localhost. This means you never have to edit your hosts file again – whenever you start a new project, your URLs and Vhosts can be assumed ready and you can start working right away, minimizing devops dramatically.

On OS X with the Homebrew package manager:

brew install dnsmasq

On your Linux distributions, download the tarball.

If you want to further automate this, you can add dnsmasq as a Vagrant plugin.

To set up dnsmasq to forward a URL pattern to your IP, and to set up your OS to forward all URL calls to dnsmasq so it can actually do this, see this excellent guide.

Conclusion

In this tutorial, we’ve learned how to automate that part of devops we all regularly deal with – setting up new Vhosts and their domains. It’s only a couple of minutes on every project, especially with packages like Homestead Improved, but it’s still a couple of minutes – and being able to quickly switch into another mock project for testing that one script you just thought of or need to try out is priceless for one’s workflow.

Did this tutorial help you? Would you have liked to get more information on certain topics? Let us know!

Comments
s_molinari

That is a pretty good little trick. Adding it to my nginx.conf now. Thanks for the tip!

I went looking for the reason why Nginx doesn't allow the variable in the error log file name and it done so by design, so that Nginx can still write out errors, even when it can't expand variables. Using SED is suggested as possible workaround.

Scott

Neil_Masters

Damn handy to know, I was looking into this last week and was unaware that I could do this in nginx.

It is specifically handy for deploying feature branches(feature-whateverChange.internal.com) and has a lot of benefits over using virtual directories.

swader

Great idea!

Victorknust

In my case I have .com and .com.br that point to the same directory, how to set it in apache?

abdmaster

Superb stuff. Thanx for great info.

For my windows Xampp I did something like this

<VirtualHost *:80>
    ServerName vhosts.fqdn
    ServerAlias *.lv5.dev
    VirtualDocumentRoot "C:/xampp/htdocs/php/laravel5/%1/public"
</VirtualHost>

And worked just fine smile

swader

You definitely shouldn't redirect all .com addresses to your vhost, else you'll prevent yourself from visiting 90% of the internet's web sites. If you're only dealing with a single instance, just make a separate virtual host block for each.

kuba

Not as useful when working with virtual machines and such tools as Vagrant. You can use this as template config for all VMs, but still need to update hosts file at host OS with VM IP.

swader

Not if you use dnsmasq which can do this automatically for you.

baf

With vagrant, the plugin vagrant-hostmanager works wonders. Automatically updates your host file so you don't have to worry about it.

Works particularly well when you have a separate Vagrant install for each project too, just start up the box and your hosts file is automatically updated smile

kuba

Thanks, @baf. I'm definitely gonna check this one out. Perhaps you know if it's overriding whole file, along with all custom records? Or is it only adding and removing appropriate lines?

baf

It adds and remove sections. Check the bottom of my post for an example ...

There are 2 issues with my solution if you're using puphpet, which is why it isn't perfect.

  • You need to set a unique hostname for each Config.yaml
  • You need to turn off the default vhost installation that Puphpet tries to use, as that can cause some conflicts.

Nothing insurmountable and for the limited time we have it's easier to work around that rather than build our own Vagrant file.

While you're at it, the vagrant-cachier plugin is also very cool, and I'm pretty certain the vagrant-vbguest has sped things up as well.

Now to find a way to make oh-my-zsh the default inside Vagrant ...

Example hosts file.

## vagrant-hostmanager-start id: dbe4914c-9e77-47aa-b259-0e150aa15e70
192.168.43.83   gptq gptq.local.dev
## vagrant-hostmanager-end

## vagrant-hostmanager-start id: d55c4fcc-822d-42d7-a64f-99b71be55a6c
192.168.43.83   october-test october-test.local.dev
## vagrant-hostmanager-end
kuba

Once again - thanks a lot, @baf. I'm rather a Vagrant beginner and your tips are really helpful. smile

swader

Priceless, thanks for this!

abdmaster

@swader While in my previous comment I said it worked for me, for some reason it is half working on a different machine with Xampp. Dynamic is working but it is not getting/reading the .htaccess file, so the rewrite engine is not working and cannot view any other links. Could you help me out?

# Slim Framework v2 (Using Auto Virtual Hosts)
<VirtualHost *:80>
    ServerName vhosts.fqdn
    ServerAlias *.slim.dev
    VirtualDocumentRoot "D:/abid/php/slim/%1/www"
    <Directory ~ "D:/abid/php/slim/.*/www">
        DirectoryIndex index.php
        Options Indexes FollowSymlinks MultiViews
        AllowOverride All
        Order allow,deny
        Allow from all
        Require all granted
    </Directory>
</VirtualHost>
swader

Sorry, I don't do XAMPP and wouldn't be able to effectively debug it.

Recommended

Learn Coding Online
Learn Web Development

Start learning web development and design for free with SitePoint Premium!

Get the latest in PHP, once a week, for free.