Generate server block (v-hosts) for nginx dynamically

HiramHiram
4 min read

Hey pals, I will show you how to create a basic shell script so you can dynamically generate nginx virtual hosts or "subdomains" for every site you want to put online. There are two examples, one for your node or whatever app that runs locally, the second is for static websites.

Reversed proxy subdomain example

If you are running a node app on a specific port this basic example will help you to create and connect your site. The following is the stub file we will use subdomain.stub:

server {
        listen 80;
        listen [::]:80;

        index index.html index.htm;

        server_name {{DOMAIN}}.yoursite.com;

        location / {
                proxy_pass http://localhost:{{PORT}};
                proxy_http_version 1.1;
                proxy_set_header Upgrade $http_upgrade;
                proxy_set_header Connection 'upgrade';
                proxy_set_header Host $host;
                proxy_cache_bypass $http_upgrade;
        }
}

Now let's create the generator.sh shell script:

#!/bin/bash

SED=$(which sed)
CURRENT_DIR=$(dirname $0)

# check the domain is valid!
PATTERN="^(([a-zA-Z]|[a-zA-Z][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z]|[A-Za-z][A-Za-z0-9\-]*[A-Za-z0-9])$"
if [[ "$1" =~ $PATTERN ]]; then
  DOMAIN=$(echo $1 | tr '[A-Z]' '[a-z]')
  echo "Creating hosting for:" $DOMAIN
else
  echo "invalid domain name"
  exit 1
fi

CONFIG="$CURRENT_DIR/$DOMAIN.yoursite.com"
cp $CURRENT_DIR/subdomain.stub $CONFIG
$SED -i "s/{{DOMAIN}}/$DOMAIN/g" $CONFIG
$SED -i "s/{{PORT}}/$2/g" $CONFIG

echo "The subdomain has been successfully generated"

cp $CONFIG "/etc/nginx/sites-available"
ln -s "/etc/nginx/sites-available/$DOMAIN.yoursite.com" "/etc/nginx/sites-enabled"

echo "The subdomain has been moved to nginx to sites-available and symlinked to sites-enabled"

In your terminal you run the command as follows: ./generator.sh <DOMAIN> <PORT>

In order to run this script you have to make it executable with this command: chmod u+x generator.sh otherwise you won't be able to execute it.

Now it's ready to run, let me explain what it does:

  • Stores SED as a variable

  • Stores the current location

  • Creates a domain pattern and then compares it with the 1st parameter (DOMAIN)

  • Defines CONFIG location, then clones the subdomain.stub file into the previous definition

  • Replaces the {{DOMAIN}} value inside the cloned configuration

  • Replaces the {{PORT}} value inside the cloned configuration

At this point our virtual host is ready to be published

  • Copies the new virtual host file into "/etc/nginx/sites-available" which is the folder for every available site with nginx

  • Then creates a symlink from the previous location to /etc/nginx/sites-enabled which is the folder for every enabled site with nginx

Once this is done you need to ensure virtual hosts definitions are valid. You can do this by running nginx -t in your terminal. If everything went well now you only need to reload/restart nginx so changes can be applied. nginx service reload/restart.

Static site subdomain example

This example is for static websites (not SSR)

subdmain.stub:

server {
        listen 80;
        listen [::]:80;

        root /var/www/{{DOMAIN}};
        index index.html index.htm index.nginx-debian.html;

        server_name {{DOMAIN}}.yoursite.com;

        location / {
                try_files $uri $uri/ /index.html = 404; #This line redirects to index for correct react router usage
        }
}

Then we use basically the same generator.sh script than before:

#!/bin/bash

SED=$(which sed)
CURRENT_DIR=$(dirname $0)

# check the domain is valid!
PATTERN="^(([a-zA-Z]|[a-zA-Z][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z]|[A-Za-z][A-Za-z0-9\-]*[A-Za-z0-9])$"
if [[ "$1" =~ $PATTERN ]]; then
  DOMAIN=$(echo $1 | tr '[A-Z]' '[a-z]')
  echo "Creating hosting for:" $DOMAIN
else
  echo "invalid domain name"
  exit 1
fi

CONFIG="$CURRENT_DIR/$DOMAIN.yoursite.com"
cp $CURRENT_DIR/subdomain.stub $CONFIG
$SED -i "s/{{DOMAIN}}/$DOMAIN/g" $CONFIG

echo "The subdomain has been successfully generated"

cp $CONFIG "/etc/nginx/sites-available"
ln -s "/etc/nginx/sites-available/$DOMAIN.yoursite.com" "/etc/nginx/sites-enabled"

echo "The subdomain has been moved to nginx to sites-available and symlinked to sites-enabled"

The only difference is that we no longer require a PORT, so you will run the command like this: ./generator.sh ${DOMAIN} (Do not forget to make the script executable)

From here the sky is the limit my friend, you could easily create your own stubs/snippets with many variables as needed. Also they only contain the minimum requirements to be served by nginx, have fun adding more specifications.

BONUS!! 🤯

If you read my previous post Easy node apps deployment with PM2 let me show you how you could integrate this virtual host generator to a pm2 deployment set up file ecosystem.config.js:

module.exports = {
  deploy: {
    production: {
      user: USER,
      host: HOST,
      ref: BRANCH,
      repo: REPO,
      path: PATH,
      'post-setup': `npm install && cd setup/ && chmod u+x generator.sh && ./generator.sh ${DOMAIN}`
    }
  }
};

Now you only have to create a /setup folder inside your project with the subdomain stub as well as the generator script and that's it. Ready to be executed after setup.

This post was originally published in my dev.to blog: https://dev.to/eichgi/generate-server-block-virtual-hosts-for-nginx-dynamically-1fpp

0
Subscribe to my newsletter

Read articles from Hiram directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Hiram
Hiram

senior software engineer | cloud engineer