Apache Dynamically Configure and Create Multiple Virtual Hosts with mod_vhost_alias

If you ever find yourself repeating the same set of virtual hosts configuration with only minimal differences in directives like ServerName, DocumentRoot and Directory then there are some ways to reduce the configuration code. Whether you are serving multiple domains or subdomains in Apache HTTPD Web Server, there are a couple of ways to dynamically generate virtual hosts to avoid writing lots of same boilerplate configuration code.

The mod_macro module is one good solution for instance, that allows us to write less and do more with the help of templates. The problem we just described, can be solved with mod_macro perfectly. But in this article we’ll discuss another module called mod_vhost_alias to solve the same problem.

mod_vhost_alias

The mod_vhost_alias module allows us to create multiple arbitrary number of virtual hosts dynamically. What this means is that we won’t have to configure multiple virtual hosts by writing one block for each of them, as long as they are mostly similar. Let’s look at an example of multiple virtual hosts for different domains and subdomains:

<VirtualHost *:80>
  ServerName foo.example.com
  DocumentRoot /var/www/foo.example.com/html
</VirtualHost>

<VirtualHost *:80>
  ServerName bar.example.com
  DocumentRoot /var/www/bar.example.com/html
</VirtualHost>

<VirtualHost *:80>
  ServerName bobdob.com
  DocumentRoot /var/www/bobdob.com/html
</VirtualHost>

Generally you’d have a few other directives like ErrorLog, CustomLog, etc. but you can already see that there’s a pattern forming across all the virtual host blocks. Apart from certain differences (server name and its usage in document root) that we should be able to set dynamically, they’re mostly similar. Imagine if we had 10 other such virtual hosts, that’d be a lot of repetition. Let’s see how we can keep it short and simple with mod_vhost_alias.

Note: You’ll need to enable the module first. Since I’m on Ubuntu I’ll just do an a2enmod vhost_alias.

Once the module is enabled, all we have to do is replace the blocks above with this:

VirtualDocumentRoot /var/www/%0/html

That’s it! Don’t forget to restart the server.

We can put the above line in our main config (httpd.conf or apache2.conf) inside or outside of a <VirtualHost> container. In this case, it’s outside. Putting it inside a <VirtualHost> will have a different behaviour, we’ll discuss that in a bit.

The VirtualDocumentRoot directive is similar to DocumentRoot in terms of defining the root folder path from where Apache will serve files. The difference is that DocumentRoot comes from the core module where as VirtualDocumentRoot comes from mod_vhost_alias allowing us to configure for dynamic mass virtual hosting.

One interesting thing you’ll notice is the %0. That is an interpolation token or specifier similar to printf function if you’re familiar with that. %0 gets interpolated with the canonical name for the server or the self-referencing URL. This canonical name or self-referential URL is determined by the UseCanonicalName directive which is Off by default which means the value for %0 will resolve to the client request’s Host header (hostname and port). So for a bunch of request URLs, lets see what the document root and the file path served will look like:

URL: http://foo.example.com/directory/file.html
Document root: /var/www/foo.example.com/html
Document/file: /var/www/foo.example.com/html/directory/file.html

URL: http://bar.example.com/directory/file.html
Document root: /var/www/bar.example.com/html
Document/file: /var/www/bar.example.com/html/directory/file.html

URL: http://bobdob.com/directory/file.html
Document root: /var/www/bobdob.com/html
Document/file: /var/www/bobdob.com/html/directory/file.html

Any instance of VirtualDocumentRoot in the main server config means the matching of a request to the document root is handled by mod_vhost_alias and not the core module of Apache. This means that VirtualDocumentRoot in the main server config will disable any other <VirtualHost> container defined.

But if VirtualDocumentRoot were defined inside another <VirtualHost>, that would not happen. The virtual host request matching would happen the default way. But that would lead to another challenge where for name-based virtual hosts, the first name-based virtual host will get picked up (default virtual host) if no server name match is found. So you’ll have to make sure the VirtualDocumentRoot virtual host block is the first virtual host in appearance order. This won’t be a problem for IP-based virtual hosts. Let’s look at an example to understand better:

<VirtualHost *:80>
  ServerName foo.example.com
  ServerAdmin [email protected]
  DocumentRoot /var/www/foo.example.com/html
</VirtualHost>

<VirtualHost *:80>
  VirtualDocumentRoot /var/www/%0/html
</VirtualHost>

If our configuration looks like above, then pretty much for all *.example.com subdomains or other domains, the first virtual host will always be picked up since its the default named vhost. To fix this we’ll have to make sure the second block appears before the first one. But for IP-based virtual hosts, this won’t be a problem:

<VirtualHost 111.22.33.44:80>
  ServerName foo.example.com
  ServerAdmin [email protected]
  DocumentRoot /var/www/foo.example.com/html
</VirtualHost>

<VirtualHost 111.22.33.55:80>
  VirtualDocumentRoot /var/www/%0/html
</VirtualHost>

Since the virtual host will be matched or picked for different IP connections (of the server) altogether, serving of one over the other is never a problem.

What if we use ServerName with VirtualDocumentRoot ?

If we did something like this:

<VirtualHost *:80>
  ServerName foo.example.com
  VirtualDocumentRoot /var/www/%0/html
</VirtualHost>

Then ServerName would do its job in the request matching process, but the interpolated value for %0 will continue to depend upon UseCanonicalName. This means if UseCanonicalName is set to Off then the request’s Host header value will be used for %0 interpolation. And if it is set to DNS then reverse DNS lookup will happen on the server’s IP on which the client connection is received. This reverse lookup hostname will be eventually use to interpolate %0.

Is %0 the only interpolation specifier I can use ?

Nope, there are quite a few. Let’s go through them. First let’s go through the formats allowed:

  • %% – Inserts a %.
  • %p – Inserts the port number of the canonical server name or the self-referential URL resolved based on UseCanonicalName value.
  • %N.M – Inserts the whole or part of the hostname from the canonical server name (or the self-referential URL).

From the third option above, N represents the whole or part of the server name or IP address (we will see an example of IP address below) in the dotted format. The M is optional and defaults to 0. M selects the character part from N. In the %0 specifier we’ve been using till now, it represents the N and refers to the whole part or name of the server name. Hence, the entire server name or IP address is used for interpolation.

Following are all the different forms for the %N.M format:

Value of N or MInterpretation
0the whole name
1the first part
2the second part
-1the last part
-2the penultimate part
2+the second and all subsequent parts
-2+the penultimate and all preceding parts
1+ and -1+the same as 0

Note: If N or M is greater than the number of parts available a single underscore is interpolated.

For a request to http://www.example.com/directory/file.html let’s see the different forms of interpolation for the document root:

VirtualDocumentRoot /var/www/%0
# Document Root: /var/www/www.example.com
# Document/File: /var/www/www.example.com/directory/file.html

VirtualDocumentRoot /var/www/%2+/%2.1/%2.2/%2.3/%2
# Document Root: /var/www/example.com/e/x/a/example
# Document/File: /var/www/example.com/e/x/a/example/directory/file.html

VirtualDocumentRoot /var/www/%2+/%2.-1/%2.-2/%2.-3/%2
# Document Root: /var/www/example.com/e/l/p/example
# Document/File: /var/www/example.com/e/l/p/example/directory/file.html

VirtualDocumentRoot /var/www/%2+/%2.1/%2.2/%2.3/%2.4+
# Document Root: /var/www/example.com/e/x/a/mple
# Document/File: /var/www/example.com/e/x/a/mple/directory/file.html

Did you say something about IP Address interpolation above?

Just like VirtualDocumentRoot the mod_vhost_alias module provides us with VirtualDocumentRootIP directive that is the same, except it uses the IP address of the server on which the client connection is made, for the interpolation (instead of the server name). So if the connection is made on say 164.92.156.80, then this is how the interpolation would look like:

VirtualDocumentRootIP /var/www/%1/%2/%3/%4
# Document Root: /var/www/164/92/156/80

What are the other configuration directives provided by mod_vhost_alias?

VirtualScriptAlias interpolated-directory|none

Similar to VirtualDocumentRoot in the sense that it specifies the location that will be interpolated by Apache and used to find CGI scripts for execution (via mod_cgi).

VirtualScriptAliasIP interpolated-directory|none

Just like VirtualScriptAlias except that it uses IP address of the server on which the connection is made for interpolation instead of the server name. So in the interpolation sense, it is similar to VirtualDocumentRootIP.

How will I know which request is being served for which host or server name in the logs?

The following format strings or interpolation specifiers in LogFormat will help:

  • %A – Local IP Address
  • %V – The self-referential URL or server name (controlled via UseCanonicalName).

What are the advantages and disadvantages of mass vhosting with mod_vhost_alias?

Advantages:

  • Makes the configuration code for multiple virtual hosts smaller, easier to maintain.
  • Adding a new virtual host or removing one doesn’t require re-configuring Apache and restarting it. It is now just a matter of creating the appropriate DNS entries for different hostnames and creating the appropriate document root folders on the machine.

Disadvantages:

  • Different log files or varying configurations for each server name is not possible. If this must be achieved, then use multiple name-based virtual hosts as shown above. As far as log files are concerned, too many log files can breach the file descriptor limits anyway. You can split the log file into multiple copies based on the hostnames using the split-logfile tool.

Is there anything else that I should keep in mind ?

Just a few notes:

  • Using mod_alias or mod_userdir will override the functionality of mod_vhost_alias.
  • The DOCUMENT_ROOT environment variable passed to CGI scripts are set by the core module. mod_vhost_alias doesn’t set any value in this variable. Hence any scripts depending upon getenv('DOCUMENT_ROOT') may get a misleading value.

Leave a Reply

Your email address will not be published.