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 webmaster@localhost
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 webmaster@localhost
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 onUseCanonicalName
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 M | Interpretation |
0 | the whole name |
1 | the first part |
2 | the second part |
-1 | the last part |
-2 | the 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 viaUseCanonicalName
).
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
ormod_userdir
will override the functionality ofmod_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 upongetenv('DOCUMENT_ROOT')
may get a misleading value.