Did you know you could launch multiple small websites from the same server and keep them isolated from each other? Plus, you could link them to multiple domain names / subdomains and apply free SSL / TLS from Let’s Encrypt.
I currently run multiple portfolio websites from one $10 / month virtual private server (VPS).
Nginx Proxy Manager does a lot of the heavy lifting by acting as a single entry point to my VPS and redirecting users to correct hosts.
I use docker to cleanly containerize websites on a single server.
Our goal in this tutorial is to launch two simple websites and access them through different subdomains.
Let’s launch the first website.
Buy a VPS
Get yourself a VPS droplet from Digital Ocean, Linode, or Vultr. For this tutorial, I’ll use a 1 CPU / 1 GB RAM VPS that costs $5/month. I’m going to install Ubuntu 20.04 as the operating system on the server.
Purchase a cheap domain name
Get yourself a cheap $0.99 domain name. It can be anything as we’re using it for the sake of this tutorial.
Access the domain name through the domain registrar and add an ‘A’ record to the domain DNS. Point the record to the IP of your VPS and set the name to a wildcard
*. This means that ANY subdomain that is accessed in the browser will be pointed towards your server.
These changes can take a half hour or more to propagate.
SSH and Install Docker
SSH into your server.
apt update and
apt install curl to update packages and install curl from the package manager.
Let’s install Docker and Docker Compose:
curl -fsSL https://get.docker.com -o get-docker.sh sudo sh get-docker.sh apt install docker-compose
This tutorial assumes a basic knowledge of docker and docker-compose.
Running nginx proxy manager container
We’re going to pull the Nginx Proxy Manager image and run it in a container.
The commands below create a new directory called
nginx-proxy. Then we change to that directory and create an empty
docker-compose.yml file. We then open the file in the nano text editor.
mkdir nginx-proxy cd nginx-proxy touch docker-compose.yml nano docker-compose.yml
Let’s copy and paste the docker-compose configuration for Nginx Proxy Manager taken directly from their github readme.
--- version: '3' services: app: image: 'jc21/nginx-proxy-manager:latest' restart: unless-stopped ports: - '80:80' - '81:81' - '443:443' environment: DB_MYSQL_HOST: "db" DB_MYSQL_PORT: 3306 DB_MYSQL_USER: "npm" DB_MYSQL_PASSWORD: "npm" DB_MYSQL_NAME: "npm" volumes: - ./data:/data - ./letsencrypt:/etc/letsencrypt db: image: 'jc21/mariadb-aria:latest' restart: unless-stopped environment: MYSQL_ROOT_PASSWORD: 'npm' MYSQL_DATABASE: 'npm' MYSQL_USER: 'npm' MYSQL_PASSWORD: 'npm' volumes: - ./data/mysql:/var/lib/mysql
Save the configuration file.
If you’re going to run this in production, I suggest changing the database username / password above in the docker-compose configuration. For this tutorial, I’m leaving it as it is.
Docker-compose is going to create two containers: the application and a database in order to run Nginx Proxy Manager.
Notice that we are exposing ports 80, 81, and 443 (ssl / tls) from the application to the docker host.
Let’s run this configuration and create the two containers. We’re bringing the docker containers ‘up’ and running both of them in ‘detached’ mode.
docker-compose up -d
Let’s check the status of the running containers:
To make it easier to see, let’s pipe the output to
less with a
-S flag so none of the lines are wrapped:
docker ps | less -S
We see two running containers (Press ‘q’ to exit this view):
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS > 694cdad1ec02 jc21/nginx-proxy-manager:latest "/init" About a minute ago Up About a minute 0.0.0.0:80-81->80-81/tcp, :::80-81->80-81/tcp, 0.0.0.0:443->443/tcp, :::443->4> dd69467b4049 jc21/mariadb-aria:latest "/scripts/run.sh" About a minute ago Up About a minute 3306/tcp
Ports 80, 81, and 443 are exposed to our host machine from the Nginx Proxy Manager container.
Setting up Nginx Proxy Manager Login
Note down your VPS IP address. Yours will be different than mine. I’ll use 18.104.22.168 in this tutorial. Use yours.
22.214.171.124 in your browser. You’ll get a page that shows:
That’s the page that is exposed on port 80. When you type in any IP or website in the browser, the browser automatically adds a port 80.
126.96.36.199 typed in the browser is really
Note that the webpage is insecure. You’ll see a padlock with a red line through it.
The login page for Nginx Proxy Manager is located on port 81. Access
188.8.131.52:81 and a login page should appear.
Login using the default credentials:
Email: firstname.lastname@example.org Password: changeme
You’ll be asked to change your email and your password.
Try to access the domain name you registered in the browser. If all the DNS changes propagated, you should see:
Our first order of business is to set the login page as a subdomain and to secure it.
184.108.40.206:81 to be accessible through
manager.selfhostbaby.xyz and to be ssl / tls secure.
Click on Hosts => Proxy Hosts => Add Proxy Host
Put in the address you wish to set up in the ‘Domain Names’ field. When a user types that in, it should forward them to port 81 on the VPS IP (220.127.116.11). I’m also going to enable blocking common exploits.
If I access
manager.selfhostbaby.xyz, the login page comes up. However, the page is insecure. The padlock is not green and there’s no tls / ssl encryption.
Let’s fix that by adding automatic FREE encryption from Let’s Encrypt through Nginx Proxy Manager.
Edit the proxy host you created and access the SSL tab. Select the ‘Request a new SSL Certificate with Let’s Encrypt’ option from the dropdown.
Make sure to force the SSL:
Now try accessing
https://manager.selfhostbaby.xyz again. Your login page is now secure! There’s a green padlock.
This is exactly how we’ll set up another website in this remaining tutorial. We’ll isolate each website in a docker container and expose a port, similar to how port 81 was exposed to the docker host. We’ll then set up a subdomain through Nginx Proxy Manager and point it to the new port, and secure it with Let’s Encrypt.
A simple NGINX webserver
Linuxserver has an Nginx docker image that we’ll use to set up an example website.
Note that they provide a docker-compose script for setup. SSH into your docker host (your VPS).
mkdir nginx cd nginx touch dockercompose.yml nano dockercompose.yml
Copy and paste this configuration into the
--- version: "2.1" services: nginx: image: ghcr.io/linuxserver/nginx container_name: nginx environment: - PUID=1000 - PGID=1000 - TZ=Europe/London ports: - 4000:80 - 4001:443 restart: unless-stopped
Note that I’m mapping docker host port 4000 to the docker container port 80 and port 4000 to port 443. The default was 80 and 443 but those ports are already being used by the Nginx Proxy Manager and that is what we want anyways.
Now run this docker compose file and let it set this container up in a detached mode.
docker-compose up -d
docker ps and note down the container ID. Note that it is running.
I can access this container web server by typing in:
You’ll see something like this. You’re running a web server on your VPS!
Let’s create a subdomain for it and secure it with Let’s Encrypt using Nginx Proxy Manager.
https://manager.selfhostbaby.xyz and add a new proxy.
Click on the SSL tab and add the Let’s Encrypt certificate.
blog.selfhostbaby.xyz. The site should load up and it is also secured by Let’s Encrypt (green-pad lock).
You can repeat this process for setting up websites for any other containers you want. Heck, you can run the same nginx docker-compose file but rename the container within the
docker-compose.yml to something else. That’s a brand new webserver that you’re serving on a single VPS!
You’re probably thinking that this web server message is boring and it would be nice to put your own content there instead.
The cool thing about these docker containers is that you can ‘jump’ into them.
docker exec -it nginx bash
-i flag enters an interactive container mode. The
-t enters a simulated terminal mode.
-it simply combines them together. You could have written them separately like this:
Then goes the running container name, which is
nginx and then the command you want to run.
We want to run
bash to access the terminal. Some flavors of linux won’t have bash installed.
In that case, use
sh instead of
Notice that your command line prefix changed! It’s something like this:
You are now inside of the nginx container, logged in as the user
root and that long number is your container id.
Browse around the container. It is totally isolated from the host. If you
cd config/www, that is where the webserver is serving files. It is serving
Go ahead and edit this file and refresh
blog.selfhostbaby.xyz. It changed! You can serve a static website from this
Play around and make more running containers from the Nginx docker image. Point them to different subdomains using Nginx Proxy Manager.
The power is in your hands. You can run multiple Wordpress websites like this or other open source services. You can have them all running from one single VPS server.