How to host multiple websites on a single server

John Kealy
Nerd For Tech
Published in
5 min readMay 25, 2021

--

An approach to hosting an almost unlimited number of web applications on one VPS server instance – for pennies

Photo by Carl Heyerdahl on Unsplash

As a largely self-taught full-stack developer, handling my own small-scale DevOps has been part and parcel of my daily work, especially for personal projects. I always have a few ideas floating around that need testing, as well as portfolio pieces to show potential employers, not to mention my personal website. But since I’m usually pretty broke, and each site gets minimal (if any) visitors, a dedicated VPS for each site would be insanity.

In this article, I’m going to show you the easiest way to deploy your web application to a VPS server, and then keep piling on more apps. I’ll assume that you already know what a VPS is, and have an idea of how to obtain an instance of one from a cloud provider.

What technologies?

You might have heard of Apache, Nginx, or even Traefik. These are all capable of the task, but the learning curve can be harsh.

My life became a hundred times easier when I discovered CaddyServer, and I urge you to check it out. We’ll use CaddyServer to create something called a Reverse Proxy. A reverse proxy is where the magic happens, and CaddyServer will perform this magic in one line of code.

I won’t talk about specific languages or frameworks in this article. You might be using any of Laravel/Rails/Wordpress/Ghost/Express/React/Spring/Anything; but as long as you can serve your web app to a port, the process here will be more or less the same.

What cloud provider?

My recommendation is DigitalOcean. For about $5 per month, you can hosts all the websites you like on one VPS, called a “droplet”. Just bear in mind that, obviously, $5 isn’t going to cut it for any significant amount of traffic. You can also shop around for other budget offerings, like Linode or the AWS free tier.

Setting up your DNS

Chances are you’ve bought a domain before, but getting them set up right can still be tricky. KISS (Keep it simple stupid!) it. Create A records. I’ve always found this to be the easiest way, rather than messing with Nameserver settings, Aliases, etc.

Assign an A record to your root domain, and give it the IP address of your VPS/droplet. You’ll need to wait a little. If you’re not sure whether the change has propagated yet, run

dig example.com

The bonus of using A records is that even though you must pay for each apex domain, most registrars will allow you to add subdomains to your heart’s content. Assign any number of subdomains to the same IP address, and our reverse proxy will route the traffic using the domain name itself as a map.

Et Voilà, multiple websites using one server, and also one domain. I find this really useful for testing prod environments, or for hosting my backends at a subdomain, e.g. api.example.com.

Okay, let’s set up our Reverse Proxy

I usually use Docker at this stage, but for the purposes of simplicity, let’s hold off on that. Once your VPS is initialised with whatever cloud provider you’ve chosen, ssh in. This will be something like

ssh root@<ip-address>

NOTE: It’s a good idea to set up a non-root user as soon as possible.

Now head over to the CaddyServer install page, and follow the instructions that pertain to your VPS’s operating system.

After installing, CaddyServer should (hopefully) be running. Check its status with

sudo systemctl status caddy

In /etc/caddy you’ll find the magical Caddyfile. This very simple text file will control our Reverse Proxy. You’ll find some boilerplate in this file, and feel free to create a backup of this, but you probably won’t need it.

Replace the whole file with this very simple configuration:

{
acme_ca https://acme-staging-v02.api.letsencrypt.org/directory
}
example.com {
reverse_proxy localhost:8000
}

Easy, right?

Caddy will now associate the port 8000 on localhost with the domain example.com. You’ll need to add your own domain name here in place of example.com. You might be wondering about the acme_ca code—we’ll come to that momentarily.

Now, reload Caddy:

sudo systemctl reload caddy

Setting up the first web app

Now that Caddy is ready and has bound port 8000 to the http port 80 and the https port 443 (this happens under the hood), let’s set up our web app. I’m not going to go into how to do this, all I’ll say is that for whatever language and framework you’re using, you’ll need to run the production code over a specified port. For example, if I wanted to run a Django application with Gunicorn, I would run something like

gunicorn config.wsgi:application --bind 0.0.0.0:8000

or if I were running a Quasar Framework application, it might be

cd dist/ssr/ && PORT=8000 npm run start

It is assumed that you can start an application and serve it over a particular port. Remember to match whatever port you use with the one you used in the Caddyfile — this is fundamental.

SSL/TLS/https considerations

This is an easy one. CaddyServer does https by default. It’s awesome.

Under the hood, Caddy is using Let’s Encrypt. When you’re getting things set up and debugging, you don’t want to be requesting a new certificate each time — you’ll run up against daily limits.

That’s why we added this line in the Caddyfile:

acme_ca https://acme-staging-v02.api.letsencrypt.org/directory

You’ll need to “accept the risk” in your browser to access your site. So if you visit your domain and you hit this warning, that’s supposed to be happening.

When you’re happy the app is running, remove the acme_ca code entirely. That’s all you need to do to serve your sites over https.

NOTE: If the browser hangs when you try to access your domain, one thing to check is whether your firewall is allowing access to the the http/s ports.

Setting up subsequent web apps

Once the first application is running, you can simply do the same thing for as many applications as you like. Add a new entry to the Caddyfile, give it a port, and then serve another app to that new port.

So for example, if you wanted a backend Django application at api.example.com, and also a frontend Vue application at example.com, your Caddyfile might be:

example.com {
reverse_proxy localhost:3000
}
api.example.com {
reverse_proxy localhost:8000
}

Then you’d run Django on port 8000, and Vue on port 3000. Once you reload Caddy, the new applications will auto-request a new SSL certificate each.

I hope this article helps you get up and running with Reverse Proxies for hosting multiple applications on the same server. Till next time!

If you’d like to go a step further, and see some of these ideas at work, please check out my open source project, Djengu.

--

--