Scripting Apache virtual host creation

in Linux, Apache, bash, vhost

Virtual hosting is one of the most important things to happen in web hosting over the recent years. It allows a single IP to be associated with multiple websites. Though there are many panel solutions that offer virtual host management, it's nice to know what's going on and even better to actually be in control. The tradeoff is that small mistakes can cause all of your sites to go down until you fix them -- unless you automate.

First things first: what is a virtual host in terms of Apache configuration? Depending on your distro, the configuration can be stored differently. In some cases it's stored in the bottom section of your httpd.conf file, in others as files in a directory. In all cases, there should be a section that defines a virtual host. Let's assume you were to create a simple virtual host for the domain example.com

Do note that the article is biased towards RedHat / CentOS directory naming because that's what I mainly use. Debian based users will probably have to replace instances of httpd in directory names with apache2.

You should see the following configuration somewhere: 

<Virtualhost 1.2.3.4:80="">
  DocumentRoot "/var/www/vhosts/example.com"
  ServerName example.com
  ServerAlias example.com
  <Directory "/var/www/vhosts/example.com">
    Allow from all
    Options -Indexes
  </Directory>
  ErrorLog /var/log/httpd/vhosts/example.com/error_log
  LogLevel warn
  CustomLog /var/log/httpd/vhosts/example.com/access_log common
</Virtualhost>

As you can see from the above, there are four main variables here :

  • the ip to listen to ( 1.2.3.4 in the example, you can use * to use any available )
  • the domain name ( example.com )
  • the location of the files to serve ( /var/www/vhosts/example.com )
  • the location of log files ( error log, access log )

In order to be able to change the above via a script, we need to assign some placeholders. Thus the above can easily be transformed to the following :

<VirtualHost *:80>
  DocumentRoot "/var/www/vhosts/%%DOMAIN%%"
  ServerName %%DOMAIN%%
  ServerAlias %%DOMAIN%%
  <Directory "/var/www/vhosts/%%DOMAIN%%">
    allow from all
    Options -Indexes
  </Directory>
  ErrorLog /var/log/httpd/vhosts/%%DOMAIN%%/error_log
  LogLevel warn
  CustomLog /var/log/httpd/vhosts/%%DOMAIN%%/access_log common
</VirtualHost>

As you can see, some of the variables have been hardcoded. That is by design - you want to keep a structure that is the same across all your domains to keep things tidy and controllable. So in the skeleton code above we assume that : 

  • Our domain will listen to any available IP to the server
  • Files for domains will be under the directory /var/www/vhosts/[domain-name] ( eg /var/www/vhosts/another.example.com )
  • Logs will be kept in the /var/log/httpd/vhosts/[domain-name] folder

Those are ( in my opinion ) sensible defaults, and are independent of distro specifics (well, the log directory might be kept in /var/log/apache2 in your distro - you can rewrite to accomodate that if you want). Create a file containing the above skeleton code, and place it somewhere in your system (my personal preference is in /opt so something like /opt/skeletonhost ).

So to initialize the structure, you should create the appropriate folders, and permissions:

# mkdir /var/www/vhosts
# chown root:users /var/www/vhosts
# mkdir /var/log/httpd/vhosts
# chown root:users /var/log/httpd/vhosts
# chmod 750 /var/log/httpd/vhosts

That should take care of the basic folder structure. Now to the fun part: make Apache read external files for virtual hosts. My personal preference is /etc/httpd/vhost.d. So create that:

# mkdir /etc/httpd/vhost.d
# chown root:root /etc/httpd/vhost.d
# chmod 700 /etc/httpd/vhost.d

Now it's time to tweak Apache's configuration to actually handle what gets in that directory.

Fire up your favorite editor, and edit /etc/httpd/conf/httpd.conf and add the following lines :

#
# Load config files for the vhost directory "/etc/httpd/vhost.d".
#     
Include vhost.d/*.conf
You can put this directive almost anywhere in the file, but I'd recommend just under the similar directive for conf.d (search for it in the file, there's only one instance)

After you restart Apache, it'll pickup any *.conf file in the /etc/httpd/vhost.d directory. All we need now is the actual script to put definitions in there!

Place the following into /usr/sbin with a filename you can remember, eg /usr/sbin/newhost :

#!/bin/bash
###################################################################
### CONFIGURATION AREA - ALL PATHS ARE ABSOLUTE, NO TRAILING /  ###
###################################################################

# Define the location of the skeleton vHost
skeleton="/opt/skeletonhost"

# Define the location where apache looks for vhost configs
confdir="/etc/httpd/vhost.d"

# Define where the domain folders should exist
vhostdir="/var/www/vhosts"

# Define where the apache logs for this domain should exist
logdir="/var/log/httpd/vhosts"

###################################################################
### WORK AREA - DONT EDIT BELOW THIS LINE UNLESS YOU CAN FIX IT ###
###################################################################

die () {
    echo >&2 "$@"
    exit 1
}

# Check if we have an argument, exit if not
[ "$#" -eq 1 ] || die "Need a domain name as argument (only)"

# Replace domain name into skeleton
sed "s/%%DOMAIN%%/$1/g" "$skeleton" > $confdir/$1.conf

# Create folder for domain
mkdir $vhostdir/$1
chown root:users $vhostdir/$1
chmod 775 $vhostdir/$1

# Create an empty index file to keep things private
touch $vhostdir/$1/index.html
chown root:users $vhostdir/$1/index.html
chmod 774 $vhostdir/$1/index.html

# Create empty log files for apache
mkdir $logdir/$1
touch $logdir/$1/access_log
touch $logdir/$1/error_log
chown root:users $logdir/$1/access_log
chown root:users $logdir/$1/error_log
chmod 740 $logdir/$1/access_log
chmod 740 $logdir/$1/error_log

# Reload apache config
service httpd graceful

# Let the user know something good just happened
echo "Domain creation of $1 complete."
echo "You can place your files in $vhostdir/$1"
echo "You can view your logs in $logdir/$1"

Just chmod this to 700 after you save it to make it executable, and you're set :

# chmod 700 /usr/sbin/newhost

Now you can simply enter the following command, and all necessary files / directories, and even an empty index.html will be created, and Apache reloaded in order to make the domain available :

#newhost example.com

You'll also get a confirmation message printed back at you, with the directories you need to know about (where files/logs are hosted) users group is also granted read permission on log files. 

Hope this helps someone save tons of time -- I'm now officially using it on all my servers for hassle free domain initialization. I should have the corresponding MySQL script in a future post, and a script to combine them (since you most likely want both the domain files and a DB to do something useful). 

Until then, enjoy :)

UPDATE: MySQL script can be found here