How to host multiple websites on a single server

John Kealy
May 25 · 5 min read

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. .

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, 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 you’ll find the magical . 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 on with the domain . You’ll need to add your own domain name here in place of . You might be wondering about the 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 — 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 :

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 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 , give it a port, and then serve another app to that new port.

So for example, if you wanted a backend Django application at , and also a frontend Vue application at , your 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.

Nerd For Tech

From Confusion to Clarification

Nerd For Tech

NFT is an Educational Media House. Our mission is to bring the invaluable knowledge and experiences of experts from all over the world to the novice. To know more about us, visit https://www.nerdfortech.org/. Don’t forget to check out Ask-NFT, a mentorship ecosystem we’ve started

John Kealy

Written by

Programmer, weatherman, dreamer...

Nerd For Tech

NFT is an Educational Media House. Our mission is to bring the invaluable knowledge and experiences of experts from all over the world to the novice. To know more about us, visit https://www.nerdfortech.org/. Don’t forget to check out Ask-NFT, a mentorship ecosystem we’ve started

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store