Virtual Hosts
In my blog article "Websites in Minutes", I talk about the steps I used to put together a memorial website for a recently-passed friend. I volunteered to create that site in part because I knew that I could construct it and eventually release it to the internet in minutes. Such is the power of an internet-exposed Linux system running an Apache webserver. But, to build and release the site took some preparation and configuration of that system. I'd like to tell you how I did it. |
|
The Naming of Names
Before I start setting up the new site, I had to chose some names; URL names, email names, path names, and the like. It helps that I had a starting point for all the names I had to choose. |
|
|
Those names helped me decide on these names:
|
Once I had decided on what to call the various new components, I contacted my domain registrar and arranged to lease the amemorial.ca domainname. This registration process took some time to complete (36 hours, by my estimate), so while I waited, I proceeded to build my "cooker" version of the new website.
Cooker
When I first put together a website, I start off in a "cooker" site. Only hosts within my LAN can access the cooker site, permitting me to work on it without having it exposed to the internet. For expediency's sake, my "cooker" resides on the same server as my production websites; I don't have a standalone website-development environment. Yes, this is poor practice, but, with caution, is practical in small-site environments. |
|
In building these sorts of sites, I start from the "inside", and work "outward", so that I satisfy any dependencies before they become an issue. In this case, that means that I
- ensured that I had the proper server software (Drupal, Apache, MySql, ISC Bind, etc.) installed and running correctly,
- built the required new Drupal databases in MySQL,
- built the Drupal CMS definition that would use those databases,
- built the Apache website definition that would invoke Drupal for the site, and
- built the DNS alias that would connect my server's lan IP address to the sitename, and finally
- build the content of site through web access to the new domain.
Drupal Database definition
The first step in setting up the Drupal databases for my cooker site is to create the database in MySQL. To do this, I used the mysqladmin command (under the global authority of my already defined DbAdmin user), and created the drupal6_amemorial database.
- To create the database that this new Drupal site will use, I issued the command:
-
mysqladmin -u DbAdmin -p create drupal6_amemorial
This database will serve as both the cooker site's database (while I develop the site), and the production site's database (when I'm ready to go live). Since there is no chance of conflict between the "cooker" site and the "production" site, I'm safe to use the same database for both. Otherwise, I'd have name the database something else, and "cloned" it to the production database when I was ready.
The second database step is to ensure that a suitable MySQL "user" exists to access and manage this database, and that the user has the appropriate MySQL permissions set. This we do as DbAdmin through the mysql command, with a single SQL statement.
- I used the mysql command (with DbAdmin authority) to
-
GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER ON drupal6_amemorial.* TO 'drupal'@'localhost' IDENTIFIED BY 'password';
Of course, I set password to some real password value; for this article, I'll just show it as password.
Drupal Site definition
Now that I have the cooker site's Drupal database defined to MySQL, I can proceed on and configure Drupal to use it.
My Drupal installation resides in /srv/httpd/htdocs/drupal6, and all the site configuration files are held in subdirectories of this directory. To hold the site configuration file for my cooker.my.lan Drupal website, I created a /srv/httpd/htdocs/drupal6/sites/cooker.my.lan directory, and populated it with an empty /files subdirectory, and a settings.php file.
- In the settings.php file, I coded:
-
$db_url = 'mysqli://drupal:password@localhost/drupal6_amemorial'; $db_prefix = ''; $update_free_access = FALSE; $base_url = 'http://cooker.my.lan'; // NO trailing slash! ini_set('arg_separator.output', '&'); ini_set('magic_quotes_runtime', 0); ini_set('magic_quotes_sybase', 0); ini_set('session.cache_expire', 200000); ini_set('session.cache_limiter', 'none'); ini_set('session.cookie_lifetime', 2000000); ini_set('session.gc_maxlifetime', 200000); ini_set('session.save_handler', 'user'); ini_set('session.use_cookies', 1); ini_set('session.use_only_cookies', 1); ini_set('session.use_trans_sid', 0); ini_set('url_rewriter.tags', '');
Here, the $db_url PHP variable names the site's database interface details, including the MySQL access method, the Drupal database user and password, and the Drupal database. The $base_url PHP variable names the leading part of the URL under which all the cooker site's pages will be found. The other PHP variables and function calls set various site performance and behaviour values; I let them default to the settings provided in the Drupal example settings.php file.
Apache VHOST definition
Since I want the webserver to serve up a different, unique set of pages when accessed through it's fully qualified domain name, I had to alter my Apache configuration to define cooker.my.lan as a "Virtual Host".
You see, a webserver can serve up different pages based on the hostname in the URL the web browser is asking for. This isn't the default mode for the Apache webserver, though, and thus needs special configuration.
- To tell Apache the configuration for my new website, I edited the /etc/httpd/httpd.conf file, to include the following values:
-
LoadModule vhost_alias_module lib/httpd/modules/mod_vhost_alias.so <VirtualHost *:80> ServerName cooker.my.lan ServerAdmin lew@my.lan DocumentRoot "/srv/httpd/htdocs/drupal6" ErrorLog "/var/log/httpd/error_log" CustomLog "/var/log/httpd/access_log" common </VirtualHost> <Directory "/srv/httpd/htdocs/drupal6"> AllowOverride All </Directory>
First, Apache needed the vhost_alias_module to parse the request URL, so that Apache could determine the hostname. Without this, Apache would only look at and act apon the page path part of the URL.
Next, I defined the VirtualHost stanza for the site. The crucial parts were the ServerName, which Apache used to match to the hostname in the request URL, and the DocumentRoot, which pointed Apache at the Drupal directory as the root directory for all page paths within the virtual domain.
Finally, for the Drupal CMS that I used for the site, I defined the Apache access permissions for the directory in which the Drupal installation resides. Drupal had it's own permission set, and I wanted those permissions to apply.
Internal DNS definition
I now need my lan's DNS to recognize cooker.my.lan and resolve it to the internal IP address of my webserver.
I run a local DNS using ISC Bind. This provides me with both a caching DNS for my LAN's internet use, and a flexible name service for my LAN. Consequently, I defined cooker.my.lan within my named database file /var/named/DB.my.lan.
- In /var/named/DB.my.lan, I have
-
$ORIGIN . my.lan IN SOA server.my.lan. root.server.my.lan. ( 2011032000 ; serial 28800 ; refresh (8 hours) 7200 ; retry (2 hours) 604800 ; expire (1 week) 86400 ; minimum (1 day) ) NS server.my.lan. A 192.168.255.1 MX 10 server.my.lan. $ORIGIN my.lan. server A 192.168.255.1 MX 10 server cooker CNAME server
This definition built a DNS 'A' record for my server, pointing the server.my.lan at the proper IP address (192.168.255.1). The 'cooker' entry built a 'CNAME' record within my DNS to point cooker.my.lan to the same IP as server.my.lan, making cooker.my.lan an alias for server.my.lan.
- Had I not run Bind, I could have done the same thing through the /etc/hosts file. The hosts entry would have been simpler:
-
192.168.255.1 server.my.lan server cooker.my.lan cooker
This one line is the equivalent of two DNS 'A' records; it points server.my.lan (or server, if referenced from within the my.lan domain) and cooker.my.lan (or cooker) at IP address 192.168.255.1.
With either setting (/etc/hosts or ISC Bind's config), I now had a unique, fully-qualified domain name accessible only within my lan that resolved to the internal IP address of my webserver.
cooker.my.lan Site Management
With all this preparation complete, I could finally access my cooker website, and start configuring it. Since I used a web browser to perform this process, I launched Firefox and pointed it at http://cooker.my.lan/install.php.
- To my delight, I saw the first page of the interactive Drupal site configuration process:
The first configuration choice was one of language. I clicked on the "Install Drupal in English" link to start the installation. Drupal accepted this, and proceeded to the Verify Requirements step.
- The Drupal installation process quickly streamed through this, and the following two steps ("Set up database" and "Install site"), and finally stopped at the Configure site page.
- In the Configure site page, I altered the default Site name value, and provided appropriate values for the rest of the configuration form.
- With all the new values in place, I clicked the Save and Continue button at the bottom of the page, and the installation script continued to the end.
- Once this installation process completed, the new Drupal site delivered an email into my inbasket:
-
Account details for Admin at In Memorium (approved) From: lew@my.lan To: lew@my.lan Admin, Your account at In Memorium has been activated. You may now log in by clicking on this link or copying and pasting it in your browser: http://cooker.my.lan/?q=user/reset/1/1303053329/ffacc1885aba7a34e4fff29fdf9df4bq This is a one-time login, so it can be used only once. After logging in, you will be redirected to http://cooker.my.lan/?q=user/1/edit so you can change your password. Once you have set your own password, you will be able to log in to http://cooker.my.lan/?q=user in the future using: username: Admin
With this preliminary work behind me, I now took my time and customized the site into the proper memorium for my friend. It took most of a day, but by the time my domain registration was approved, I had the cooker site prepared as if it were amemorial.ca.
Production
So, now that amemorial.ca was mine, I wanted to move the website onto the internet. Like the "cooker" site before, I had to take specific steps to make it available:
|
Drupal Site definition
The main value of the "cooker" site is that it allowed me to customize the contents of the drupal6_amemorial databases without requiring an external domain name. However, once I acquired the rights to amemorial.ca and had completed the configuration of the "cooker" website, I had to connect the drupal6_amemorial database to the Drupal configuration for the new website.
So, to hold the site configuration for my amemorial.ca Drupal website, I created a /srv/httpd/htdocs/drupal6/sites/amemorial.ca directory, and populated it with copy of the cooker.my.lan/files subdirectory, and the settings.php file from the cooker.my.lan drupal configuration.
- I then altered the sites/amemorial.ca/settings.php file, making the single necessary change to make this file serve amemorial.ca
-
$db_url = 'mysqli://drupal:password@localhost/drupal6_amemorial'; $db_prefix = ''; $update_free_access = FALSE; $base_url = 'http://amemorial.ca'; // WAS 'http://cooker.my.lan' ini_set('arg_separator.output', '&'); ini_set('magic_quotes_runtime', 0); ini_set('magic_quotes_sybase', 0); ini_set('session.cache_expire', 200000); ini_set('session.cache_limiter', 'none'); ini_set('session.cookie_lifetime', 2000000); ini_set('session.gc_maxlifetime', 200000); ini_set('session.save_handler', 'user'); ini_set('session.use_cookies', 1); ini_set('session.use_only_cookies', 1); ini_set('session.use_trans_sid', 0); ini_set('url_rewriter.tags', '');
Apache VHOST definition
Like the "cooker" site, I needed Apache to recognize amemorial.ca (and the alternate www.amemorial.ca) and serve up the proper web pages for that domain. This meant that I had to add another Apache VirtualHost to my configuration.
- In my /etc/httpd/httpd.conf, I added the appropriate VirtualHost stanza, and restarted my web server.
-
<VirtualHost *:80> ServerName amemorial.ca ServerAlias www.amemorial.ca ServerAdmin webmaster@amemorial.ca DocumentRoot "/srv/httpd/htdocs/drupal6" </VirtualHost>
The ServerName names amemorial.ca as the hostname that the VirtualHost would act as. Additionally, the ServerAlias names www.amemorial.ca; this VirtualHost would serve both amemorial.ca and www.amemorial.ca from the same set of webpages.
Apache would use the ServerAdmin email address webmaster@amemorial.ca in any 'native' error messages it would present to the web browser.
With that, my internet-facing web server was ready to serve up pages for the amemorial.ca domain. But, I still had other "internal" facilities to set up before I could go "live".
Sendmail Virtual Domains
The Internet RFCs name several email addresses that an Internet-facing site should have.
- If the site provides an email server, the domain must have a postmaster@ email address,
- If the site provides an web server, the domain must have a webmaster@ email address, and
- If the site provides for internet-facing user services, the domain must have a abuse@ email address,
My amemorial.ca domain would offer specific services to external users using a web server that generated emails, so I had to add all three email addresses to my email server. Additionally, amemorial.ca would use a unique email address (admin@) to send administrative emails from, so I needed sendmail to handle that address as well.
Virtual Host
For this new website, I wanted all incoming email to be sent to the hostname amemorial.ca. A DNS MX record would ensure that email clients know which IP address to send @amemorial.ca email to, but my Sendmail MTA needs to know that it is permitted to receive email addressed to a amemorial.ca recipient. In other words, I needed to tell Sendmail to process emails addressed to the host amemorial.ca.
This took an addition to my /etc/mail/local-host-names file, adding the host amemorial.ca to the list of hostnames that this Sendmail will receive for.
-
# names of hosts for which we receive email my.lan amemorial.ca
Now, Sendmail will service email sent to @my.lan and @amemorial.ca addresses.
Virtual Users
Once sendmail accepts an email for one of the addresses in my new domain, it has pass it on to the proper local user.
- I added both the new "virtual" email addresses, and a default error (for email addresses not already specified) to my /etc/mail/virtusertable
-
admin@amemorial.ca lew abuse@amemorial.ca abuse postmaster@amemorial.ca postmaster webmaster@amemorial.ca webmaster @amemorial.ca error:5.7.0:550 No such user here
The local lew, abuse, postmaster and webmaster users are already defined elsewhere; the virtusertable entries here just connect the new @amemorial.ca email addresses to those users. Additionally, the final rule causes Sendmail to reject email addressed to any @amemorial.ca user not already listed in virtusertable; this would help to reduce the amount of spam I'd receive at the new address.
Internet-facing DNS definition
With those changes, I was ready to open the floodgates, and let people connect to the amemorial.ca website. This meant that I could now make the changes to my public DNS to direct amemorial.ca and www.amemorial.ca to my public IP address, and to direct all email for @amemorial.ca addresses to that same address.
So, I asked my public DNS service to open a set of DNS records for domain name amemorial.ca, with an A record pointing amemorial.ca at my IP, a CNAME record pointing www.amemorial.ca at amemorial.ca, and an MX record pointing amemorial.ca at my public IP as well. Once those DNS changes percolated through the Domain Name system, both the website and the email for my memorial site became available to the internet at large.
Drupal Cron job
The last step necessary was to create a cron job that would run every five minutes to retrieve and discard the http://amemorial.ca/cron.php webpage. Drupal uses this task to initiate it's internal scheduling facility.
With that final change, amemorial.ca became a stable, available website to memorialize my late friend.