Creating LAMP VM – Multiple Sites

This is part of a series of posts on creating your own LAMP virtual machine for development purposes. Up until now we have focused just on installing everything and making sure it works. But how do we switch between versions of PHP? How do we set up multiple sites to test different applications? Now we can finally get into that and how to tie it all together so we can develop using whatever tools we wish.

Estimated time (this post only): 45 minutes

Introduction

Even though we are covering the configuration of multiple sites on your Apache server here, this should not be used for a production machine with multiple users (like a shared host) since it doesn’t address several security concerns. But for a development machine, home server, or other type of private server, this method should be fine.

Do you have to do this? No. Technically you could start developing on your VM right now. Switching between versions of PHP could be accomplished by either changing the file extensions as we did earlier, or editing the httpd.conf file as mentioned in the last two articles. If you just want to start working immediately, you can skip pretty much everything in this article except the Webmin stuff.

One possible security issue for production machines I found is that it is necessary to expose the paths to each PHP-FPM installation in .htaccess files (as you will see later). The old method of using mod_fastcgi would allow us to use an alias for the CGI application instead, but as of this writing there is no supported method of installing mod_fastcgi for Apache 2.4 (hours of searching did uncover an unsupported patch, but I wasn’t comfortable using an unofficial build). This can be mitigated by using secure passwords, limiting which IP addresses sudo-enabled users can login from, and being sure you have proper file permissions on the directories in question. So maybe I’m being overly paranoid.


Name Resolution

Notes On DNS

For those familiar with DNS, much of this may seem elementary. But for those who don’t know, you cannot have one DNS server for one network and another server for another network without setting up forwarders. Whichever DNS server responds first will take priority and if it can’t resolve the host/domain name or find a DNS server to forward the request to, the request will fail. There are many ways to configure name resolution, all depending on various factors, such as whether this VM is accessible to the rest of your network, whether you are hosting it on a mobile device, whether you have existing DNS servers, etc.

Method #1: if you only need to connect to the VM from your host system (or one or two others on your network), you could skip all of the DNS stuff in this article and just use your hosts file (see next section). This is easy and doesn’t require the installation of anything else, but it means you won’t be able to access your VM websites from any system which doesn’t have a properly-configured hosts file. This is also probably best if your host system is a laptop which connects to multiple networks.

Method #2: if you have DNS servers on your network but you wish to use a different domain for the VM (i.e. “test.local”), you can setup BIND on the VM and forwarders on the existing DNS servers. This can be done whether you have the VM configured with a host-only adapter or one in bridged mode, but typically you would only do this when configured in bridged mode. Below is a simple example. The first image is the physical layout of the network, while the second is a basic idea of how the DNS servers would be configured.

Network Diagram 1

DNS Diagram 1

Method #3: similar to method #2 above, if you want to register these test sites as sub-domains of your existing network domain, you can skip installing BIND on the VM. Instead, just create A records for every site you are hosting on the VM. This requires that you have existing DNS servers already and that your VM has an adapter configured in bridged mode.

Network Diagram 1

DNS Diagram 3

I will only briefly cover methods 2 and 3, because both require making changes to your existing DNS servers, which is beyond the scope of this article. If you have questions, feel free to leave a comment below.

HOSTS File

Skip this section if you are going to use DNS for name resolution. There are plenty of articles out there on how to edit your hosts file. Rackspace has a good one here that covers both Windows and Linux. So read that for more details. You will need a separate entry for every domain and sub-domain you are setting up for testing on your VM, all pointing to the static IP address you set up for it earlier. For example, if you plan to use the domain “test.local” with sub-domains “site1” and “site2” all running on the VM’s IP address of 192.168.56.10:

192.168.56.10 test.local
192.168.56.10 site1.test.local
192.168.56.10 site2.test.local

You can skip everything about DNS below if using this method.

Install DNS Server

If you want to use method 2 above, you will need to install BIND (change firewall zone name if necessary):

sudo yum install bind bind-utils
sudo service named start
sudo chkconfig named on
sudo firewall-cmd --permanent --zone=work --add-service=dns
sudo firewall-cmd --reload

For now we are not going to bother configuring BIND. We will get into that later.

Add Entries to Existing DNS Server(s)

If you need to forward requests from your existing DNS servers to the VM (method 2 above) or create A records (method 3 above), I can’t give you detailed instructions since every DNS server is different. Check out this TechNet article if you have a Windows DNS server. Another option may be to install a DNS server (like MaraDNS) on your host machine and setup one forwarder for the domain of your VM and other forwarders to public DNS servers or your network’s DNS servers. If you are on a home network and have a supported router, you may be able to use DD-WRT’s DNS server to setup a domain forwarder, but I’ve never used it for that purpose.


Webmin

I won’t force you to do everything in text editors any more than necessary from this point forward. I’m sure you’ve gotten the general idea by now anyway. Let’s use Webmin to make things easier. Why didn’t we install this from the start? Several reasons. 1) You really should know how to do things from the command-line. 2) We wanted to install bleeding-edge software from source. 3) Even Webmin has limitations.

To install Webmin, do the following first (most of this is probably installed already):

sudo yum install perl perl-Net-SSLeay openssl perl-IO-Tty
sudo nano /etc/yum.repos.d/webmin.repo

In this new file paste the following:

[Webmin]
name=Webmin Distribution Neutral
#baseurl=http://download.webmin.com/download/yum
mirrorlist=http://download.webmin.com/download/yum/mirrorlist
enabled=1

After saving that file, do the following:

cd /usr/local/src
sudo wget http://www.webmin.com/jcameron-key.asc
sudo rpm --import jcameron-key.asc
sudo yum install webmin
sudo service webmin start
sudo chkconfig webmin on

You could use it locally with eLinks. To test that it works just do:

elinks https://localhost:10000

It is very difficult to navigate with a text browser however. So just quit eLinks with “q” and let’s open up that port in the firewall so we can access it from the graphical browser on our host system. First, create a new service for firewalld:

sudo nano /etc/firewalld/services/webmin.xml

Paste the following in this new file:

<?xml version="1.0" encoding="utf-8"?>
<service>
  <short>Webmin</short>
  <description>Allows access to the Webmin server if installed.</description>
  <port protocol="tcp" port="10000"/>
</service>

Exit and save the file. Then do the following (change zone name if necessary):

sudo firewall-cmd --permanent --zone=work --add-service=webmin
sudo firewall-cmd --reload

By default, Webmin uses the root account, which we locked earlier. So re-enable it temporarily:

sudo passwd -u root

Now, back on your host system’s browser, visit the Webmin site at “https://192.168.56.10:10000” and log in with the root account and password (if you forgot it you can always do “sudo passwd root”). Under the Webmin section, go to “Webmin Users.” You may notice there are many options for how to handle Webmin users. You could store the accounts in a MySQL database, create separate Webmin users from the Unix users, create Webmin users as aliases of Unix users, etc. Since this is not a production machine, we will keep things simple and just allow our normal user account (which already has sudo access) to login as a root user. Go to “Configure Unix User Authentication” and select the option “Allow users who can run all commands via sudo to login as root.” Save changes, then expand the “System” section on the left, go to “Users and Groups,” select the “root” account, and change “Password” to “No login allowed” to disable direct root logins again.

Allow Webmin sudo Logins

Disable root Account

After saving your changes, logout of Webmin. Try logging back in as root. It should fail now. But you will be able to login as your normal user with full root access. Take the time to browse the various built-in Webmin modules. There are many others you can download if you wish.

As of this writing, the default Java security settings may prevent some of the modules from loading correctly in your browser, so you may need to add this server to your security exceptions list in the Java Control Panel on your host system. The only modules I noticed that I had to do this for were the SSH ones, which I didn’t need since I prefer Bitvise to using SSH in the web browser… after all, if you have a browser crash for any reason, this can cause real problems.

You may notice that the Apache module is listed under the “Un-used Modules” section. That’s because it isn’t installed where Webmin expects it to be. So click the module, then the link to “module configuration” and change everything in the “System configuration” section to match your setup. Look at the screenshot below for what should be the correct settings if you’ve followed this guide so far:

Webmin Apache Configuration

If all went well, after saving these settings, you will be able to edit your Apache configuration under the “Servers” section in Webmin. But first we should configure our new DNS server (if necessary as mentioned earlier).


BIND Configuration

Important: because BIND is running on a VM on a trusted host or network in this example, I will not be overly concerned with security. It is not an authoritative server for any domains on the Internet, so even if fully exposed to the Internet with our DNS ports open to public interfaces through FirewallD and our physical router/firewall forwarding DNS requests to this system, the security risk is minimal. If this were a server directly exposed to the Internet with DNS records for public domains (i.e. mycompanywebsite.com) securing your BIND configuration would be crucial. Still, I will be covering some basic security tips nonetheless.

In Webmin, under the “Servers” section, click “BIND DNS Server” and then “Addresses and Topology.” By default, BIND is probably configured to only allow requests from localhost. So change this:

BIND Addresses Settings

Save your settings. Even that doesn’t allow queries for any client other than the local host (BIND’s default configuration is basically set to allow nothing, which is secure to the point of paranoia). So you need to decide who can send DNS requests to this server. If you need remote computers (not logging in via VPN with an IP address on the local network) to be able to resolve host names on this system, you need to allow queries to “any” which means any IP address can send name resolution requests to this server. Setting this to “any” does not necessarily expose the system to requests over the Internet, since your router would also have to be forwarding the requests to this system. So for simplicity sake, if you are sure your network is secure, you may just want to use that. Or you can set it to a specific subnet like “192.168.56.0/24”. Or, you can use an ACL. That last option is the most flexible, so let’s do that.

Click on “Access Control Lists.” Now assign a name (can be anything) for the new ACL, and add any and all IP addresses or subnets you wish to be able to query your DNS server. Below is a screenshot containing several examples. Note that “localhost” refers only to the local system, while “localnets” refers to all local networks. In other words, if you don’t want to risk BIND being exposed to the Internet, you could just include “localnets” and nothing else, and in most cases this should be good enough (in the interests of full disclosure I should tell you the single IP address in this example is the one for the White House official website):

BIND ACL

Save again. Now click on “Zone Defaults” and look for where it says “Allow queries from…” where you will now enter the name of your new ACL. Alternately, if you didn’t use an ACL, you could just enter the IP address(es), subnet(s), and/or localhost/localnets as desired here.

BIND Allow Queries

Save again. Now, let’s set up forwarders to external DNS servers to handle Internet hostname requests. This may not be strictly needed depending on your network configuration, as mentioned earlier. Click “Forwarding and Transfers” and add some public DNS server addresses. In this example, I use OpenDNS, but you could also use Google’s Public DNS if you prefer.

OpenDNS Forwarders

Save again. Now you can finally add the zone(s) for the websites you will be adding. For this example, let’s assume we want to create a new domain named “test.local” for which the VM will handle all DNS requests. Click “Create Master Zone” and then fill out the form as follows (changing things like domain and email address as appropriate):

BIND New Zone

After clicking the “Create” button, click “Return to zone list” at the bottom, then “Create Master Zone” again to create the reverse lookup zone. Note that this step is optional, especially if you are not using the host-only adapter for the VM. In this example, I am assuming a 24-bit subnet mask; you may have to change this to mask your network settings.

Reverse Lookup Zone

After clicking the “Create” button, click “Return to zone list” at the bottom, then click the zone we created earlier (“test.local” in this example). Now click “Address” and create a record for the name server you set in the previous steps, pointing to the static IP address of the VM like so:

NS A Record Creation

Now add another address record for our first test site. I called mine “site1” but you can use a more descriptive name.

Another A Record

After you are done setting up DNS, click “Apply Configuration” in the top-right corner.

Note: if you are not forwarding requests from your existing DNS server and rather using this as your primary DNS server, you will now have to change your host system’s primary network card to point to the IP address of the VM as the first DNS server (don’t forget to set your internal DNS, router, ISP DNS, or public DNS servers as secondary/tertiary so you don’t lose Internet access when the VM isn’t running). For example, on Windows:

Windows Custom DNS Settings


Apache Virtual Hosts

Many guides out there will have you creating a separate user for each host. You can do this if you wish, but it isn’t strictly necessary. One advantage of having a separate user per site is you can have FTP default to that user’s home directory which will host the site. Alternately you can just have your own user be the owner of all the sites. I will cover both methods in this article and the next. If you want separate users per site, do:

sudo useradd -d /home/site1.test.local site1
sudo passwd site1
sudo su - site1
mkdir /home/site1.test.local/htdocs
mkdir /home/site1.test.local/cgi-bin
logout

If you don’t want a separate user, but rather want to make your existing user the owner of the new site, do (replace “jdoe” with your username):

sudo mkdir /home/site1.test.local
sudo chown jdoe:jdoe /home/site1.test.local
mkdir /home/site1.test.local/htdocs
mkdir /home/site1.test.local/cgi-bin

You could create each virtual host using the “Create virtual host” tool in Webmin, but honestly it is faster to just edit the Apache configuration files and then make changes as desired. In Webmin, open “Apache Webserver” under “Servers” and then open the “Global configuration” tab. Click “Edit Config Files” to edit httpd.conf. Search for and un-comment the following line:

Include conf/extra/httpd-vhosts.conf

After saving, click “Edit Config Files” again. Now, in the drop-down menu at the top you can choose to edit “httpd-vhosts.conf” so open it. Delete the two example hosts in this file (you can keep the comments if you want). If you want both HTTP and HTTPS access to this named virtual host, paste the following and save the file:

<Directory "/home/site1.test.local/htdocs">
allow from all
Options None
Require all granted
AllowOverride All
</Directory>

<Directory "/home/site1.test.local/cgi-bin">
Options None
AllowOverride None
Require all granted
</Directory>

<IfModule alias_module>
ScriptAlias cgi-bin /home/site1.test.local/cgi-bin
</IfModule>

<VirtualHost *:80>
DocumentRoot /home/site1.test.local/htdocs
ServerName site1.test.local
ErrorLog /home/site1.test.local/error_log
LogLevel warn
CustomLog /home/site1.test.local/access_log "common"
</VirtualHost>

<VirtualHost *:443>
DocumentRoot "/home/site1.test.local/htdocs"
ServerName site1.test.local
ServerAdmin nobody@nowhere.com
SSLEngine on
SSLCertificateFile "/usr/local/apache2/conf/server.crt"
SSLCertificateKeyFile "/usr/local/apache2/conf/server.key"
<FilesMatch "\.(cgi|shtml|phtml|php)$">
SSLOptions +StdEnvVars
</FilesMatch>
<Directory "/home/site1.test.local/cgi-bin">
SSLOptions +StdEnvVars
</Directory>
ErrorLog /home/site1.test.local/error_log
LogLevel warn
CustomLog /home/site1.test.local/access_log "common"
CustomLog "/home/site1.test.local/ssl_request_log"           "%t %h %{SSL_PROTOCOL}x %{SSL_CIPHER}x \"%r\" %b"
BrowserMatch "MSIE [2-5]"          nokeepalive ssl-unclean-shutdown          downgrade-1.0 force-response-1.0
</VirtualHost>

By placing the “Directory” directives outside of the “VirtualHost” directives, this ensures that the directives are consistent between the HTTP and HTTPS hosts that share the same directory. If you create the hosts using the Webmin tools instead, the “Directory” directives will be inside the “VirtualHost” directives, so you will have to ensure both hosts have the same settings for those directories. You can still use Webmin to change settings for these directories; you will just find them under “Default Server” rather than the individual virtual hosts.

It is necessary to create two separate hosts like this rather than a single one listening on all available ports in order to ensure you can access the SSL by name. This is due to limitations of SNI (you can read about SNI here if you want, but if you’ve followed this tutorial there is no need).

If you have no need for SSL support for this host, you could instead paste the following (which is slightly easier to manage in Webmin):

<VirtualHost *:80>
DocumentRoot /home/site1.test.local/htdocs
ServerName site1.test.local

<Directory "/home/site1.test.local/htdocs">
allow from all
Options None
Require all granted
AllowOverride All
</Directory>

<Directory "/home/site1.test.local/cgi-bin">
Options None
AllowOverride None
Require all granted
</Directory>

<IfModule alias_module>
ScriptAlias cgi-bin /home/site1.test.local/cgi-bin
</IfModule>

ErrorLog /home/site1.test.local/error_log
LogLevel warn
CustomLog /home/site1.test.local/access_log "common"
</VirtualHost>

After saving, click “Apply Changes” in the top-right. You can open the new hosts under “Existing virtual hosts” and change the configuration as desired, but the above settings should be good for now.

If there is a particular version of PHP you want as the default for each virtual host, you could set that by adding a new directive before </VirtualHost> (though we can also override this in .htaccess which will be explained in the next article). See either of the PHP articles (here and here) for examples. If you want to use 5.6 as your default, you could add:

<FilesMatch \.php$>
    SetHandler "proxy:unix:/opt/php-5.6.11/var/run/php.sock|fcgi://localhost"
</FilesMatch>

Now to test it, from your SSH client (or terminal), type the following (skip the first and last commands if your username is the owner of the new site’s directory):

sudo su - site1
cp /usr/local/apache2/htdocs/index.php /home/site1.test.local/htdocs/
logout

On your host web browser, you can now visit both the HTTP and HTTPS (if configured) websites at “http://site1.test.local” and “https://site1.test.local” respectively. Assuming you have followed this guide you will see the output of phpinfo(). To ensure you are viewing the correct site, search for “DOCUMENT_ROOT” and make sure the path it shows matches the one above. If the HTTPS site is instead pointing to the Apache default directory of “/usr/local/apache2/htdocs” that means there is a problem with SNI. This can occur if you built Apache against an old version of OpenSSL or used some other means to install Apache (such as yum) which didn’t come with SNI support. See the earlier article in this series for how to build Apache.

Now you can add as many virtual hosts as you want by following the above steps. Short version:

  1. Depending on the type of name resolution you chose, add an entry to your hosts file or DNS server.
  2. Create directories for the new site with appropriate permissions.
  3. Create virtual host in Apache.
  4. Apply configuration.

You may wish to delete the default SSL site “www.example.com” now that you’ve ensured that name-based virtual hosts are working via SSL (that’s why I didn’t have you delete it earlier; to make sure it didn’t take precedence).

Leave a Reply