Bash Script to Create new virtual hosts on Nginx each under a different user

This script is a modified version of the original vhost creator script for nginx posted here (Actually it was a modified version of the original version of this script).

The script below will automatically create a new user on the system and adds the nginx user to the new users group. This allows FTP access to be given to the newly created user and the user will only have access to the their site and not to all vhosts running on your server, however as long as the users group has permission to access the files then nginx will still be able to serve them.

NOTE: This setup only helps lock down access to the vhost directories on a web server hosting only static sites, if CGI of any kind (i.e. PHP) is available then this will also need to be locked down so each user has access to a CGI process or set processes running as that user.

What does the script do:

  • Creates a new system user for the site
  • Creates a new vhost config file for nginx using a basic template
  • Creates a new directory for the site, within the new users home directory
  • Adds a simple index.html file to the new directory to show the site is working.
  • Makes sure the new nginx config syntax is correct before trying to reload nginx
  • Reloads Nginx to allow the new vhost to be detected

How it works:

Under your Nginx config directory (probably /etc/nginx) you need to create two new directories:
  • sites-available
  • sites-enabled

In the “sites-available” dir goes the the individual config files for each vhost, each new vhost using a new file. Under the “sites-enabled” directory goes a symlink to the config file in the sites available directory, this gives you the ability to disable sites without removing the contents of the site or its config. This all works by adding the following line to the end of the “nginx.conf” file (within the http section)

# Load all vhosts !
include /etc/nginx/sites-enabled/*.conf;
Replacing the path of the your nginx configuration if needed.
The hosting directory for the new site can be set by modifying the WEB_DIR value at the top of the script which defaults to /var/www. This is where all vhost users home directories will get created and a public_html directory under that will be created to host the web site.

How to use it:

Simply download the tar file at the end of the this article and extract it. If your not sure how to extract a tar file either read the man pages or use the command below:

tar -xzf create_nginx_vhost.tar.gz

Once you have extracted the archive just run the create_nginx_site.sh script passing to it the domain name as the only parameter (you will need execute permissions), it will prompt you for a username and password for the new user. e.g.

./create_nginx_site.sh example.com
Creating hosting for: example.com
Please specify the username for this site?
example
Please enter a password for the user: example
Changing password for user example.
passwd: all authentication tokens updated successfully.
the configuration file /etc/nginx/nginx.conf syntax is ok
configuration file /etc/nginx/nginx.conf test is successful
Reloading nginx:                                           [  OK  ]
 
Site Created for example.com
--------------------------
Host: sebstestbox
URL: example.com
User: example
--------------------------

 The Script:

#!/bin/bash
# Author: Seb Dangerfield
# URL: http://sebdangerfield.me.uk
#
 
NGINX_ALL_VHOSTS='/etc/nginx/sites-available'
NGINX_ENABLED_VHOSTS='/etc/nginx/sites-enabled'
WEB_DIR='/var/www'
SED=`which sed`
NGINX=`sudo which nginx`
CURRENT_DIR=`dirname $0`
 
if [ -z $1 ]; then
	echo "No domain name given"
	exit 1
fi
DOMAIN=$1
 
# 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 [[ "$DOMAIN" =~ $PATTERN ]]; then
	DOMAIN=`echo $DOMAIN | tr '[A-Z]' '[a-z]'`
	echo "Creating hosting for:" $DOMAIN
else
	echo "invalid domain name"
	exit 1
fi
 
# Create a new user!
echo "Please specify the username for this site?"
read USERNAME
sudo adduser -d$WEB_DIR/$USERNAME $USERNAME
echo "Please enter a password for the user: $USERNAME"
read -s PASS
sudo echo $PASS | sudo passwd --stdin $USERNAME
 
# Now we need to copy the virtual host template
CONFIG=$NGINX_ALL_VHOSTS/$DOMAIN.conf
sudo cp $CURRENT_DIR/virtual_host.template $CONFIG
sudo $SED -i "s/DOMAIN/$DOMAIN/g" $CONFIG
sudo $SED -i "s#ROOT#$WEB_DIR/$HOME_DIR\/public_html#g" $CONFIG
 
sudo usermod -aG $USERNAME nginx
sudo chmod g+rxs $WEB_DIR/$USERNAME
sudo chmod 600 $CONFIG
 
sudo $NGINX -t
if [ $? -eq 0 ];then
	# Create symlink
	sudo ln -s $CONFIG $NGINX_ENABLED_VHOSTS/$DOMAIN.conf
else
	echo "Could not create new vhost as there appears to be a problem with the newly created nginx config file: $CONFIG";
	exit 1;
fi
 
sudo /etc/init.d/nginx reload
 
# put the template index.html file into the public_html dir!
sudo mkdir /var/www/$HOME_DIR/public_html
 
sudo cp $CURRENT_DIR/index.html.template $WEB_DIR/$HOME_DIR/public_html/index.html
sudo $SED -i "s/SITE/$DOMAIN/g" $WEB_DIR/$HOME_DIR/public_html/index.html
sudo chown $USERNAME:$USERNAME $WEB_DIR/$HOME_DIR/public_html -R
 
echo -e "\nSite Created for $DOMAIN"
echo "--------------------------"
echo "Host: "`hostname`
echo "URL: $DOMAIN"
echo "User: $USERNAME"
echo "--------------------------"
exit 0;

Download:

Please note both downloads contain exactly the same files.

Download (tar.gz)
Download (zip)

Comments or Questions:

Please feel free to leave any comments or questions in the section below.

19 thoughts on “Bash Script to Create new virtual hosts on Nginx each under a different user

  1. Hey.. thanks for this, it does exactly what I’m looking for. I’m not terribly experienced with running scripts though, when I try to run the script I get an error:

    -bash: ./create_site.sh: /bin/bash^M: bad interpreter: No such file or directory

    The only place in the script that references bin/bash is in a comment at the top, so I’m not sure why it gets stuck on this.. any help greatly appreciated!

  2. Hi, thanks for your script. I am having trouble understanding the following part:

    “… if CGI of any kind (i.e. PHP) is available then this will also need to be locked down so each user has access to a CGI process or set processes running as that user.”

    I am interested in implementing this script on a server that will certainly have PHP-FPM. What modifications are needed in this case? I don’t quite get the locking down part.

    Thanks!

    • For each vhost user you have that needs PHP create a new php-fpm pool with the user and group of that pool set to the unix user and group owning that vhost. Then php-fpm will create a new set of processes for each pool and each pool will be running under a different user effectively isolating one user from another.

      For example if you have two vhosts that reguire php:

      drwxr-x--- 5 site1 site1     4096 Mar  1 19:25 site1
      drwxr-x--- 6 site2 site2     4096 Mar  2 12:12 site2
      

      Then you would have a number of php-fpm processes that would look like so (using the ps command)

      site1     6848  0.1  1.8 180984 32140 ?        S    18:35   0:04 php-fpm: pool site1
      site1     6849  0.1  1.8 180984 32140 ?        S    18:35   0:04 php-fpm: pool site1
      site2     6850  0.2  2.2 183344 38560 ?        S    18:35   0:07 php-fpm: pool site2
      site2     6851  0.2  2.2 183344 38560 ?        S    18:35   0:07 php-fpm: pool site2
      

      Because of the permissions set on your directories the user site2 cannot access the contents of the site1 directory and the other way around. PHP is now running as a different user for each vhost (as the vhost owner) instead of php running as a user that has access to every site.

      • Great, thanks for your reply. It’s starting to make a little more sense as to how the script needs to be adjusted. I’ll take a stab at it.

        I am not very familiar with php-fpm pools. It seems new pools can be created by including a new file like: /etc/php5/fpm/pool.d/domain.conf

        What would happen if the php-fpm pool files all have the same user and groups… say “www-data”, instead of each pool having its own user and group set to owner of vhost?

        • Yes you’re right new pools can be created using the /etc/php5/fpm/pool.d/domain.conf file, us it as a template file if you like, just copy the file and rename it something new, keeping it under the pool.d directory and php-fpm will automatically load it in (as long as it’s got a .conf extension). You can then change the settings in the new file for your new php pool. Rememeber to restart/reload php-fpm after you make any changes (“/etc/init.d/php5-fpm restart” or “/etc/init.d/php-fpm restart” for non debian).

          If you run all pools under the same user “www-data” for instance then a security vulnerability in the php code used by one vhost could result in a hacker gaining the db credentials or other confidetnial data from another vhost (as the code/files for both sites can both be accessed by www-data). The same would apply if you did not trust the owners of each vhost as if all php is run as the same user then there is the potential for them to be able to write a php script to access the code under the other vhosts.

          I do have another version of the script in this post for creating sites with php support which automatically sets up a new php-fpm pool for each new vhost, it looks like it might be useful for others too. I will see if I can get time to add it to my blog in the next few days.

          • Thanks for your detailed answer. I’ll try to modify the script so I can get a little more familiar with everything, but if you could post your version of the script, it will really help to compare and ensure I am doing it right.

            Thanks again.

          • Hello,

            I think there is a bug here.
            If each website directory has permission drwxr-x—, NGINX need permission -rw-r–r– to read the file inside the /home/usersite/image.jpg. No problem with php-fpm you can define a username/group to read the filem then you can define usersite1…ok..
            But nginx runs as NGINX user, then your file must be readable for everyone

            If you remove the Read from everyone nginx stops to read the image.jpg file
            BUT…if your PHP file is Readable for everyone also, if you have a “shared wordpress hosting” and another user hosting try to read wp-config file from another user, it will be possible..

            I don’t know if I could explain the situation..
            Do you understand that?

            I think here is the breach.

            Situation: Your files inside the hosting directory is R for everyone, if another user knows the name of your user and the filename, he can read the file using PHP.

          • If Nginx runs as the user nginx and you setup my script correctly then the user nginx will get added to your new vhost users group (i.e. example_com). Then if the files inside your hosting directory are owned by the new vhost user and its group example_com, Nginx will have access without the files been world readable. For example the following would be accessible by both nginx and php:

            # ls /home/example_com/public_html/css/main.css -l
            total 4
            -rw-r----- 1 example_com example_com 10 Jul  2 21:18 main.css
            

            The other thing to keep in mind is as long as the files parent directory does not have the execute permission set for other users, then even if a file inside is world readable other users (outside of the owner and members of the owner group) won’t be able to read its contents.

          • Hmmmm

            Intersting..
            Then NGINX user will be added to websiteX.com group and it will read the files with “the owner, and group” permissions. (540) .

            Good shot…I never thought it..
            And why the permission cannot be 541 ? (world executable (x) )

            Thanks!

          • You don’t want the permissions on the home directory of the user to be other executable because this would potentially allow other users to access the files (as long as they know the file names) Only the user and group need access, no one else.

  3. Pingback: Automatically creating new virtual hosts with Nginx (Bash script) | Web & Software Development…

  4. Hi, Thanks for sharing the script. I have a VPS and am not that good with Linux so here is one lame question.

    For a server with nothing but Ubuntu, Node.js (no PHP) will this be secure enough? You said the script creates FTP account. Is there a way to also create SSH access? and can the user change their password later on?
    Secondly regarding the domain name, where would its DNS records be saved?

    • The script creates a new linux user for each vhost, if your ssh config allows all users to SSH in then they will be able to. They can also change their password via SSH using `passwd`.

      The script does not setup any DNS settings, these need to be done separately. Probably with your DNS provider (unless you manage this your self), if you don’t have a dedicated DNS provider it is probably just your domain name provider, they will probably provide a limited web based DNS editing interface to create the appropriate records. Use `nslookup` or `whois` to find out where the NS servers live(as in their hostnames, not if they reside in a quaint bungalow in a picturesque dale…) or who the registrar of the domain is respectively.

  5. Hi, I tried your script (and original one) and I realised this is not very ubuntu/debian compatible, so I write one with python. It’s on alpha stages. I just wanted to thank you for creating such a cool script. :)

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>