An approach to hosting an almost unlimited number of web applications on one VPS server instance – for pennies
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.
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
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,
ssh in. This will be something like
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
/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:
Caddy will now associate the port
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.
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
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
Caddyfile might be:
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.