Multiple Apps on One Server With Mod-Rewrite
SyntaxSeed (Sherri W)
Posted on June 20, 2019
For small companies and freelance or solo developers, hosting can be an expense which adds up over time to a hefty bill. Whether you have several VPSes or shared-hosting accounts, it makes little sense to have multiple small, experimental, or low-traffic sites on their own servers. You could serve them from multiple subdomains or subdirectories, but yuck! Domains are cheap and I like the polish of having something on it's own domain once it's public.
However, hosting companies usually only give you one web root directory. In the case of a VPS, sometimes provisioning them charges a small fee per 'app'. An app being a distinct project or site on it's own domain and with a separate web root directory (such as /www/appname/html/
).
Fake It Till You Make It
Let's look at how we can use Apache's mod-rewrite module to create several fake web roots and send traffic to them based on request domain.
Setup
First, we'll assume that you already have one domain (such as example.com) pointed to your server. It is serving pages that you place in your web root.
Next, park a second domain (example.net) to the same server or hosting account. I won't get into how domain parking works. This second domain should be serving the same pages in the same web root like an alias. Two domains to a single site.
Then in your web root, create two directories:
- site-examplecom/
- site-examplenet/
And move your existing site files into the first one. Add a placeholder index.html with some text to the second one, so we will see when one or the other site is served. Now you're ready for the magic.
Mod Rewrite Is A Multi-tool Of Awesome
Apache HTTP Server has a module called Mod_Rewrite which is usually installed by default. Make sure you have it on your server. You can give Apache instructions via a .htaccess file in your web root. These will affect the directory the file is in, and all subdirectories. Create an empty file named .htaccess in your web root, or edit the existing one. Don't put it in those site-* directories we created earlier.
Next, put the following in that file and we'll look at what it does.
<IfModule mod_rewrite.c>
RewriteEngine On
# Direct requests for example.COM:
RewriteCond %{HTTP_HOST} ^(?:www\.)?example\.com
RewriteCond %{REQUEST_URI} !^/site-examplecom/
RewriteRule ^(.*)$ /site-examplecom/$1 [L]
# Direct requests for example.NET:
RewriteCond %{HTTP_HOST} ^(?:www\.)?example\.net
RewriteCond %{REQUEST_URI} !^/site-examplenet
RewriteRule ^(.*)$ /site-examplenet/$1 [L]
</IfModule>
Mod_Rewrite takes the requested URL, and based on PCRE regular expression rules, can change the corresponding filesystem path or even rewrite to a different URL.
The <IfModule> directive will ensure that the lines inside are only executed if that module exists.
The first line inside is clear, it turns on the rewrite engine. No kidding.
Then we have two nearly identical blocks of 3 lines led by a comment. Let's examine those 3 lines.
First:
RewriteCond %{HTTP_HOST} ^(?:www\.)?example\.com
One or more RewriteCond directives can precede a RewriteRule. The rule will only be executed if the preceding conditions are met.
This line compares the incoming http host from the request headers (base domain part of the URL) to the regular expression ^(?:www\.)?example\.com
. The ^ symbol means 'starts with'. So we are checking if the request host begins with example.com with the www.
being optional and not captured to a variable. If it the host matches this regex, we move onto the next line.
Second:
RewriteCond %{REQUEST_URI} !^/site-examplecom
This condition checks if the request URI (the part after the host/domain) does NOT (the '!' negates the regex) start with /site-examplecom
. Which it will contain when we do a successful rewrite. This is important, it stops us from entering an infinite loop of doom and getting a Server 500 error.
Third:
RewriteRule ^(.*)$ /site-examplecom/$1 [L]
And finally, if the conditions were met, we perform a substitution of the requested path (url part after the host and port). Watch out: the already mapped directories are removed. So if the .htaccess file is in the top web root you get the full URL path without the leading slash. If the .htaccess file was in a test/
sub directory of the web root, you'd only be matching the pattern against the part of the URL path after test/
.
The (.*)
pattern grabs everything from the beginning ('^') to the end ('$') of the URL path, saves it in the variable $1
, then sticks it after /site-examplecom/
. The [L]
flag means to stop processing rules for this request if this one was executed.
High-Level Look
We have this block of two conditions and one rule, duplicated for our two 'apps'. This is the process:
- If it's the right http host.
- And if it's not already requesting our fake
site-*
app root. - Then append
site-example*/
to the front of the URL path.
And that's it. You should now see the two different sites if you visit the different domains. You can add another parked domain, subdirectory and block of rewrite directives to add another 'app'.
Considerations
Faking web roots has some drawbacks. Your apps are not isolated from one another. In theory they could mess with each other, and a vulnerability in one affects the others.
Your host might have terms against doing this. Though I don't see why you wouldn't be able to harness the power of the tools at your disposal.
You should keep your applications' databases separate. This will make moving and migrating apps/sites easier, and avoid collisions with tables.
Make sure that any of your code that deals with filesystem paths, takes into account that the url path and filesystem path won't match.
Using .htaccess files slows Apache down a little bit. If you have access to the main httpd configuration for the server, you should set these rules in a <directory> block there instead.
Multiple sites on one server or hosting account can eat up system resources quickly. This is why I only recommend this for small projects with very low traffic. Sites that don't warrant their own server/hosting yet.
Fun Extras
There is an excellent tool for testing Mod_Rewrite rules found on the madewithlove website.
If you'd like to strip the www off of requests and ensure that your site is only visited via the naked domain, add this (applies to all domains) before your other rules (the R=301
flag tells browsers and search engines that this is a permanent redirect, consider leaving it off till you're happy that it's working):
# Remove the www:
RewriteCond %{HTTP_HOST} ^www\.
RewriteRule ^(.*)$ https://%{HTTP_HOST}/$1 [R=301,L]
That's All Folks
I hope this little tutorial was helpful. I learned a lot writing it, and while I've been using this for many years, I'm also curious whether anyone has any gotchas I should consider or improvements I could make. Thanks for reading!
Posted on June 20, 2019
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.