Name-based virtual hosting with Nginx
It's sometimes called mass virtual hosting or simply name-based virtual hosting: using the requested server domain name to pick the right site to host. The mass part is where you do not simply configure each name but use a dynamic virtual host form the request. Each request is then mapped to a root containing the right content.
Having used this functionality in Apache I decided to give it a try in Nginx.
In Nginx you solve this with a regular expression (and then you have two problems ;-)). Using named match groups to generate the parts of the path on disk. The regular expression I use is:
~^((?<subdomain>.*)\.)?(?<domain>[^.]+)\.(?<tld>[^.]+)$
which works s follows: match the complete string only, where the subdomain is possibly there (bneijt.nl) but if it's there, it's everything but the last dot (((?<subdomain>.*)\.)?
). Both the domain and the top level domain are required and they need to have a .
in between them. I found debuggex.com a handy tool in constructing this regular expression.
When a request comes in, we simply map this to a root based on the variables created by the regular expression:
server_name ~^((?<subdomain>.*)\.)?(?<domain>[^.]+)\.(?<tld>[^.]+)$;
location / {
root /srv/http/vhost/${domain}.${tld}/${subdomain};
}
One problem we are left with is what will happen when the subdomain is empty. The root would become /src/http/vhost/example.com/
which would host the subdomain www
in a directory below the domain instead. Not what we want. To solve this, we add an if statement to the configuration. This results in the final configuration reading:
server {
server_name ~^((?<subdomain>.*)\.)?(?<domain>[^.]+)\.(?<tld>[^.]+)$;
if ($subdomain = "") {
set $subdomain "_";
}
location / {
index index.html;
root /srv/http/vhost/${domain}.${tld}/${subdomain};
}
}
Testing the configuration
During development, I had error_log /tmp/error.log;
to debug the configuration and regex in Nginx. You will see error records when no file is found and you can just place the error_log
statement inside the server
context.
To test various subdomains, add a random domain to your /etc/hosts
file, for example:
149.210.136.127 how.www.hello.com www.hello.com hello.com
After adding this rule, you can open a browser and just visit hello.com
, www.hello.com
and how.www.hello.com
. Of course you need the IP address to point to your own server (not mine). If everything works well
hello.com
should serve/srv/http/vhost/hello.com/_/index.html
www.hello.com
should serve '/srv/http/vhost/hello.com/www/index.html`how.www.hello.com
should serve/srv/http/vhost/hello.com/how.www/index.html
Loose ends
One of the thing I have not figured out is how to get rid of the if
statement. For something as common as a default value, I would expect there to be a better solution. The Nginx if
documentation also states that in most cases if is evil.
Another thing is you probably want a redirect to, or from, the www
subdomain to the plain domain. To solve this you will need per site configuration in the _
or www
folder (in Apache I used an .htaccess
file). If you can determine one rule for all the domains you vhost, then there should be no problem. But if you can't, I don't see how you can easily add the same per-domain control yet. I guess the best solution would be to add a server
section per specific domain with a rewrite rule.
If you have a solution to any of the above problems, please consider adding a comment.