Using VirtualDocumentRoot *only* if a suitable document root exists
I found a workaround which works for me.
I can use an ErrorDocument to pick up the 404 Error in a PHP script. At first, that seemed problematic. If there's no DocumentRoot, where will the script live?
I could use a URL for the error message, served from a different domain. However, in testing I found no way of knowing what the originally requested domain was.
The answer was to Alias a directory and serve the errors out of that, so my vhost looks like this
<VirtualHost *>
UseCanonicalName Off
VirtualDocumentRoot /var/www/sandboxes/domains/%0
ServerName catchall.host
Alias /errors /var/www/default/errors/
ErrorDocument 404 /errors/notfound.php
</VirtualHost>
Now, when an unconfigured domain is requested, the script at /var/www/default/errors/notfound.php is invoked instead. This script can check $_SERVER['HTTP_HOST'] to see what domain was requested. If it is actually configured, then we have a regular 404. If it's not configured, we can display some alternate error message. In my case, it's where I show a UI to help set the vhost up.
I found your question when Googling. I had exactly the same problem and applied the fix as described in Paul's answer. However, for my complex web app, I wasn't happy with routing all requests trough one single notfound.php
.
In the end, I managed to fix the problem without external scripts, only by editing my VirtualHost config.
At first, my VirtualHost was configured like this:
<VirtualHost *:80>
Usecanonicalname Off
Virtualdocumentroot /mnt/ramdisk/www/cms-%-3.0-development/
</VirtualHost>
I had URLs like client1.domainname.com
, client2.domainname.com
and whatever.domainname.com
which resolved to /mnt/ramdisk/www/cms-client1-development/
, /mnt/ramdisk/www/cms-client2-development/
and /mnt/ramdisk/www/cms-whatever-development/
However, an URL like nonexistent.domainname.com
would give me a 404 because the directory /mnt/ramdisk/www/cms-nonexistent-development/
did not exist. I wanted these subdomains to use the directory /mnt/ramdisk/www/cms-default-development/
I fixed this by using ModRewrite
and ModProxy
:
<VirtualHost *:80>
Usecanonicalname Off
Virtualdocumentroot /mnt/ramdisk/www/cms-%-3.0-development/
RewriteEngine on
RewriteCond %{HTTP_HOST} ^(.*)\.domainname\.com$ [NC]
RewriteCond /mnt/ramdisk/www/cms-%1-development/ !-d
RewriteRule (.*) http://default.domainname.com/$1 [P,L]
</VirtualHost>
What this does is: grab the subdomain (the part before .domainname.com
), insert that into the path (the %1) and proxy requests to the default URL only if the directory doesn't exist.
By using a proxy, the process is transparent and the user thinks he's visiting http://nonexistent.domainname.com, while he actually views the content from http://default.domainname.com/.
I also came across this question googling for apache2 dynamic vhost fallback and Luc'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...
My goals
- dynamic vhosting for all domains and subdomains pointing at my VPS
foo.com
should serve the same content aswww.foo.com
- fallback for unknown domains to some sort of default
- fallback for unknown subdomains of
foo.com
towww.foo.com
unless thewww
is not available, fallback to default instead
DNS
I have a couple of domains (and all their subdomains) pointing at my VPS, for example:
- foo.com
- bar.com
- foobar.com
Filesystem
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:
/var
/www
/localhost
/foo.com
/www
/bar
/bar.com
/foo
Tests
Translating my goals into testable cases:
- foo.com should be served from foo.com/www
- www.foo.com should be served from foo.com/www
- bar.foo.com should be served from foo.com/bar
- foo.foo.com should be served from foo.com/www (foo.com/foo does not exist)
- bar.com should be served from localhost (bar.com/www does not exist)
- www.bar.com should be served from localhost (bar.com/www does not exist)
- foo.bar.com should be served from bar.com/foo
- bar.bar.com should be served from localhost (bar.com/bar does not exist)
- foobar.com should be served from localhost (foobar.com does not exist)
- www.foobar.com should be served from localhost (foobar.com does not exist)
- foo.foobar.com should be served from localhost (foobar.com does not exist)
The Solution
This uses: mod_rewrite
, mod_proxy_http
and ofcourse mod_vhost_alias
.
ServerName my.domain
ServerAdmin [email protected]
<VirtualHost *:80>
ServerName localhost
VirtualDocumentRoot /var/www/localhost
</VirtualHost>
<VirtualHost *:80>
ServerName sub.domain
ServerAlias *.*.*
VirtualDocumentRoot /var/www/%-2.0.%-1.0/%-3
RewriteEngine on
RewriteCond %{HTTP_HOST} ^(.*)\.(.*)\.(.*)$ [NC]
RewriteCond /var/www/%2.%3 !-d
RewriteRule (.*) http://localhost/$1 [P]
RewriteCond %{HTTP_HOST} ^(.*)\.(.*)\.(.*)$ [NC]
RewriteCond /var/www/%2.%3/%1 !-d
RewriteCond /var/www/%2.%3/www !-d
RewriteRule (.*) http://localhost/$1 [P]
RewriteCond %{HTTP_HOST} ^(.*)\.(.*)\.(.*)$ [NC]
RewriteCond /var/www/%2.%3/%1 !-d
RewriteRule (.*) http://%2.%3/$1 [P]
</VirtualHost>
<VirtualHost *:80>
ServerName bare.domain
ServerAlias *.*
VirtualDocumentRoot /var/www/%-2.0.%-1.0/www
RewriteEngine on
RewriteCond %{HTTP_HOST} ^(.*)\.(.*)$ [NC]
RewriteCond /var/www/%1.%2 !-d [OR]
RewriteCond /var/www/%1.%2/www !-d
RewriteRule (.*) http://localhost/$1 [P]
</VirtualHost>
How does this work? There are three virtual hosts defined:
localhost
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.
sub.domain
The sub.domain vhost is taking all requests in the form of *.*.*
. By default all requests are served from /domain.com/sub
as defined by VirtualDocumentRoot /var/www/%-2.0.%-1.0/%-3
.
fallback:
The first RewriteRule
takes care of unknown domains, eg. domain.com
directory does not exist, by proxying the localhost website.
The second RewriteRule
also proxies to localhost when both the domain.com/sub
and the domain.com/www
directories are not present.
The third RewriteRule
proxies to domain.com
when domain.com/sub
does not exist. We know domain.com/www
does exist because of the second rewrite block.
bare.domain
The bare.domain vhost is taking the *.*
requests and serves them /domain.com/www
Here the RewriteRule
will proxy to localhost when domain.com
or domain.com/www
do not exist.
^$%.*!!!
I had some trouble wrapping my head around all those $
and %
signs in the RewriteCond
and RewriteRule
so I will explain about them here:
ServerAlias *.*.*
VirtualDocumentRoot /var/www/%-2.0.%-1.0/%-3
RewriteCond %{HTTP_HOST} ^(.*)\.(.*)\.(.*)$ [NC]
RewriteCond /var/www/%2.%3/%1 !-d
RewriteRule (.*) http://%2.%3/$1 [P]
- The
*
in theServerAlias
are just wildcards. - The
%n
in theVirtualDocumentRoot
are from the document name interpolation. - The
%n
in the secondRewriteCond
refer to the selections(.*)
from the firstRewriteCond
, eg. the parts of the requested domain. - The
%n
in theRewriteRule
do too. - The
$1
in theRewriteRule
refers to the selection(.*)
at the beginning of theRewriteRule
. Which captures everything from the domain till the?
in the request url. Any querystring is automatically added to the url bymod_proxy
.