{"id":160,"date":"2019-12-09T15:46:44","date_gmt":"2019-12-09T14:46:44","guid":{"rendered":"https:\/\/blog.remyblom.nl\/?p=160"},"modified":"2020-11-17T10:20:57","modified_gmt":"2020-11-17T09:20:57","slug":"apache-dynamic-vhosts-with-fallback","status":"publish","type":"post","link":"https:\/\/blog.remyblom.nl\/?p=160","title":{"rendered":"Apache: dynamic vhosts with fallback"},"content":{"rendered":"\n<p>It has been almost five years since I wrote an answer to a <a href=\"https:\/\/serverfault.com\/questions\/286773\/using-virtualdocumentroot-only-if-a-suitable-document-root-exists\/286786\">serverfault question about dynamic vhosting with fallback using Apache<\/a>. I repost it here on my very own blog, for historic reasons.<\/p>\n\n\n\n<p class=\"has-medium-font-size\"><strong><em>RemyNL answered:<\/em><\/strong><\/p>\n\n\n\n<p>I also came across this question googling for <em>apache2 dynamic vhost fallback<\/em> and Luc&#8217;s answer helped me a lot with solving my problem, but I still want to show what I did to achieve my goals, mainly because it involved some extra works and because I think it could be helpful for any future googlers\u2026<\/p>\n\n\n\n<p><strong>My goals<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>dynamic vhosting for all domains and subdomains pointing at my VPS<\/li><li><code>foo.com<\/code> should serve the same content as <code>www.foo.com<\/code><\/li><li>fallback for unknown domains to some sort of default <\/li><li>fallback for unknown subdomains of <code>foo.com<\/code> to <code>www.foo.com<\/code> unless the <code>www<\/code> is not available, fallback to default instead<\/li><\/ul>\n\n\n\n<p><strong>DNS<\/strong><\/p>\n\n\n\n<p>I have a couple of domains (and all their subdomains) pointing at my VPS, for example:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>foo.com<\/li><li>bar.com<\/li><li>foobar.com<\/li><\/ul>\n\n\n\n<p><strong>Filesystem<\/strong><\/p>\n\n\n\n<p>I have the following directories, domains contain directories with the names of the available subdomains, the www directory is required, but the config should be able to deal with the situation where it is not present. Localhost is used as default fallback:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/var\n  \/www\n    \/localhost\n    \/foo.com\n       \/www\n       \/bar\n    \/bar.com\n       \/foo<\/code><\/pre>\n\n\n\n<p><strong>Tests<\/strong><\/p>\n\n\n\n<p>Translating my goals into testable cases:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li><strong>foo.com<\/strong> should be served from <strong>foo.com\/www<\/strong><\/li><li><strong>www.foo.com<\/strong> should be served from <strong>foo.com\/www<\/strong><\/li><li><strong>bar.foo.com<\/strong> should be served from <strong>foo.com\/bar<\/strong><\/li><li><strong>foo.foo.com<\/strong> should be served from <strong>foo.com\/www<\/strong> (foo.com\/foo does not exist)<\/li><li><strong>bar.com<\/strong> should be served from <strong>localhost<\/strong> (bar.com\/www does not exist)<\/li><li><strong>www.bar.com<\/strong> should be served from <strong>localhost<\/strong> (bar.com\/www does not exist)<\/li><li><strong>foo.bar.com<\/strong> should be served from <strong>bar.com\/foo<\/strong><\/li><li><strong>bar.bar.com<\/strong> should be served from <strong>localhost<\/strong> (bar.com\/bar does not exist)<\/li><li><strong>foobar.com<\/strong> should be served from <strong>localhost<\/strong> (foobar.com does not exist)<\/li><li><strong>www.foobar.com<\/strong> should be served from <strong>localhost<\/strong> (foobar.com does not exist)<\/li><li><strong>foo.foobar.com<\/strong> should be served from <strong>localhost<\/strong> (foobar.com does not exist)<\/li><\/ul>\n\n\n\n<p><strong>The Solution<\/strong><\/p>\n\n\n\n<p>This uses: <code>mod_rewrite<\/code>, <code>mod_proxy_http<\/code> and ofcourse <code>mod_vhost_alias<\/code>.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>ServerName my.domain\nServerAdmin admin@my.domain\n\n&lt;VirtualHost *:80>\n    ServerName localhost\n    VirtualDocumentRoot \/var\/www\/localhost\n&lt;\/VirtualHost>\n\n&lt;VirtualHost *:80>\n    ServerName sub.domain\n    ServerAlias *.*.*\n    VirtualDocumentRoot \/var\/www\/%-2.0.%-1.0\/%-3\n\n    RewriteEngine on\n\n    RewriteCond %{HTTP_HOST} ^(.*)\\.(.*)\\.(.*)$ [NC]\n    RewriteCond \/var\/www\/%2.%3 !-d\n    RewriteRule (.*) http:\/\/localhost\/$1 [P]\n\n    RewriteCond %{HTTP_HOST} ^(.*)\\.(.*)\\.(.*)$ [NC]\n    RewriteCond \/var\/www\/%2.%3\/%1 !-d\n    RewriteCond \/var\/www\/%2.%3\/www !-d\n    RewriteRule (.*) http:\/\/localhost\/$1 [P]\n\n    RewriteCond %{HTTP_HOST} ^(.*)\\.(.*)\\.(.*)$ [NC]\n    RewriteCond \/var\/www\/%2.%3\/%1 !-d\n    RewriteRule (.*) http:\/\/%2.%3\/$1 [P]\n&lt;\/VirtualHost>\n\n&lt;VirtualHost *:80>\n    ServerName bare.domain\n    ServerAlias *.*\n    VirtualDocumentRoot \/var\/www\/%-2.0.%-1.0\/www\n\n    RewriteEngine on\n\n    RewriteCond %{HTTP_HOST} ^(.*)\\.(.*)$ [NC]\n    RewriteCond \/var\/www\/%1.%2 !-d [OR]\n    RewriteCond \/var\/www\/%1.%2\/www !-d\n    RewriteRule (.*) http:\/\/localhost\/$1 [P]\n&lt;\/VirtualHost><\/code><\/pre>\n\n\n\n<p>How does this work? There are three virtual hosts defined:<\/p>\n\n\n\n<p><strong>localhost<\/strong><\/p>\n\n\n\n<p>The localhost serves as a default. All requests that are not resolvable are served by localhost. Setting up a symlink from localhost to any of your domains is like setting up that site as a default.<\/p>\n\n\n\n<p><strong>sub.domain<\/strong><\/p>\n\n\n\n<p>The sub.domain vhost is taking all requests in the form of <code>*.*.*<\/code>. By default all requests are served from <code>\/domain.com\/sub<\/code> as defined by <code>VirtualDocumentRoot \/var\/www\/%-2.0.%-1.0\/%-3<\/code>.<\/p>\n\n\n\n<p>fallback:<\/p>\n\n\n\n<p>The first <code>RewriteRule<\/code> takes care of unknown domains, eg. <code>domain.com<\/code> directory does not exist, by proxying the localhost website.<\/p>\n\n\n\n<p>The second <code>RewriteRule<\/code> also proxies to localhost when both the <code>domain.com\/sub<\/code> and the <code>domain.com\/www<\/code> directories are not present.<\/p>\n\n\n\n<p>The third <code>RewriteRule<\/code> proxies to <code>domain.com<\/code> when <code>domain.com\/sub<\/code> does not exist. We know <code>domain.com\/www<\/code> does exist because of the second rewrite block.<\/p>\n\n\n\n<p><strong>bare.domain<\/strong><\/p>\n\n\n\n<p>The bare.domain vhost is taking the <code>*.*<\/code>requests and serves them <code>\/domain.com\/www<\/code><\/p>\n\n\n\n<p>Here the <code>RewriteRule<\/code> will proxy to localhost when <code>domain.com<\/code> or <code>domain.com\/www<\/code> do not exist.<\/p>\n\n\n\n<p><strong>^$%.*!!!<\/strong><\/p>\n\n\n\n<p>I had some trouble wrapping my head around all those <code>$<\/code> and <code>%<\/code> signs in the <code>RewriteCond<\/code> and <code>RewriteRule<\/code> so I will explain about them here:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>    ServerAlias *.*.*\n    VirtualDocumentRoot \/var\/www\/%-2.0.%-1.0\/%-3\n    RewriteCond %{HTTP_HOST} ^(.*)\\.(.*)\\.(.*)$ [NC]\n    RewriteCond \/var\/www\/%2.%3\/%1 !-d\n    RewriteRule (.*) http:\/\/%2.%3\/$1 [P]<\/code><\/pre>\n\n\n\n<ul class=\"wp-block-list\"><li>The <code>*<\/code> in the <code>ServerAlias<\/code> are just wildcards.<\/li><li>The <code>%n<\/code> in the <code>VirtualDocumentRoot<\/code> are from the <a href=\"http:\/\/httpd.apache.org\/docs\/2.2\/mod\/mod_vhost_alias.html\">document name interpolation<\/a>.<\/li><li>The <code>%n<\/code> in the second <code>RewriteCond<\/code> refer to the selections <code>(.*)<\/code> from the first <code>RewriteCond<\/code>, eg. the parts of the requested domain.<\/li><li>The <code>%n<\/code> in the <code>RewriteRule<\/code> do too.<\/li><li>The <code>$1<\/code> in the <code>RewriteRule<\/code> refers to the selection <code>(.*)<\/code> at the beginning of the <code>RewriteRule<\/code>. Which captures everything from the domain till the <code>?<\/code> in the request url. Any querystring is automatically added to the url by <code>mod_proxy<\/code>.<\/li><\/ul>\n","protected":false},"excerpt":{"rendered":"<p>It has been almost five years since I wrote an answer to a serverfault question about dynamic vhosting with fallback using Apache. I repost it here on my very own blog, for historic reasons. RemyNL answered: I also came across this question googling for apache2 dynamic vhost fallback and Luc&#8217;s answer helped me a lot &hellip; <\/p>\n<p class=\"link-more\"><a href=\"https:\/\/blog.remyblom.nl\/?p=160\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\"> &#8220;Apache: dynamic vhosts with fallback&#8221;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[15,14,2],"tags":[],"class_list":["post-160","post","type-post","status-publish","format-standard","hentry","category-config","category-snippets","category-vps"],"_links":{"self":[{"href":"https:\/\/blog.remyblom.nl\/index.php?rest_route=\/wp\/v2\/posts\/160","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.remyblom.nl\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.remyblom.nl\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.remyblom.nl\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.remyblom.nl\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=160"}],"version-history":[{"count":1,"href":"https:\/\/blog.remyblom.nl\/index.php?rest_route=\/wp\/v2\/posts\/160\/revisions"}],"predecessor-version":[{"id":161,"href":"https:\/\/blog.remyblom.nl\/index.php?rest_route=\/wp\/v2\/posts\/160\/revisions\/161"}],"wp:attachment":[{"href":"https:\/\/blog.remyblom.nl\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=160"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.remyblom.nl\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=160"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.remyblom.nl\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=160"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}