The goal is simple but lofty -- configure Apache for multiple SSL virtualhosts in Mac OS X Leopard.
In practice, this gets a little complicated. Here are the basic set of steps to take:
- Configure domain name resolution for the development host names
- Configure distinct IP address aliases for those host names (critical for multiple SSL Virtualhosts)
- Create the self-signed SSL certificate(s)
- Enable the correct modules and configuration files for Apache.
For my purposes, I want my host names to work much like "localhost" works. I just want a host name to point to my local computer. Basically, here's what I want:
host: localhost = ip: 127.0.0.1
host: local.lo-fi.net = ip: 127.0.0.2
host: dev.lo-fi.net = ip: 127.0.0.3
Now that you've made this decision, you can move forward.
Configuring Domain Name Resolution for the Development Host Name
This is the easiest part, by far. Simply open /etc/hosts with your favorite text editor, and add a few lines. The following is what mine will look like.
All of the "localhost" lines should already exist for you. The 2 lines that are relevant to you are the ones showing the desired ip addresses with the development domains. After you save this, if you try pinging "local.lo-fi.net", your computer will try to connect to the ip address "127.0.0.2" However, that IP address doesn't exist yet. That's next.
##
# Host Database
#
# localhost is used to configure the loopback interface
# when the system is booting. Do not change this entry.
##
127.0.0.1 localhost
255.255.255.255 broadcasthost
::1 localhost
fe80::1%lo0 localhost
#Development domains
127.0.0.2 local.lo-fi.net
127.0.0.3 dev.lo-fi.net
Configuring IP Aliases with ifconfig and launchd
Since I want to have my host names act like localhost does, I'm going to add aliases to the loopback network interface. You can do this in the terminal by typing:
Now, when you ping "local.lo-fi.net", your computer tries to contact 127.0.0.2, and you get a result. Great! This is exactly what we want -- well, kind of. When you log out or re-boot, this configuration is lost. What we really want is for this to happen at startup, so we don't have to re-configure ifconfig every time we start our computer.
sudo ifconfig lo0 alias 127.0.0.2 netmask 255.255.255.0
sudo ifconfig lo0 alias 127.0.0.3 netmask 255.255.255.0
Making this happen is one of the coolest things in this process -- at least, that's what I think. Before I get to the decided solution, I'll back up just a bit. In previous versions of Mac OS X, you could edit the /etc/iftab file, consisting of lines with the arguments you'd send to ifconfig (eg.
lo0 alias 127.0.0.2 netmask 255.255.255.0)
, and this would get picked up and work when starting up. However, in Leopard iftab is gone. What's a developer to do? One solution I found leveraged Automator, but I figured out something much more elegant.Launchd to the rescue!
Launchd is a daemon running in OS X that is responsible for lots of process management. It's designed to be a powerful replacement for other types of service management tools like inetd, rc, and even cron. What's neat is that you can create a plist configuration file pointing to the executable file you want to run, put it in a specific place, and the system executes it at the right time. What we're going to do is create a plist file for each of our network aliases. These plist files will actually execute ifconfig with the arguments we need, and do it as the privileged root user without any manual intervention.
We're going to create 2 plist files:
OK, here's the content of one of these files (net.lo-fi.local.ifconfig):
/Library/LaunchDaemons/net.lo-fi.local.ifconfig.plist
/Library/LaunchDaemons/net.lo-fi.dev.ifconfig.plist
<plist version="1.0">
<dict>
<key>Label</key>
<string>net.lo-fi.local.ifconfig</string>
<key>ProgramArguments</key>
<array>
<string>/sbin/ifconfig</string>
<string>lo0</string>
<string>alias</string>
<string>127.0.0.2</string>
<string>netmask</string>
<string>255.255.255.0</string>
</array>
<key>RunAtLoad</key>
<true/>
</dict>
</plist>
I won't go into the nitty gritty about launchd plist files, but basically this file does 3 things.
- Declares a unique Label name (net.lo-fi.local.ifconfig)
- Declares the program to run (ifconfig), as well as the arguments that follow
- Tells launchd to run this at load time
sudo chown root:wheel /Library/LaunchDaemons/net.lo-fi.local.ifconfig.plistNow, if you re-boot, these files will execute ifconfig with the arguments you need. It's so simple, it's beautiful.
sudo chown root:wheel /Library/LaunchDaemons/net.lo-fi.dev.ifconfig.plist
Self Signed SSL Certificates
I have to admit, I followed the instructions for creating a self signed certificate here, with splendid results. If you're interested in the details about how creating a self signed certificate works, it's worth your time to read it. If you don't care, and just want something to work, you probably won't need more than what I show you below. However, here's the high level overview of what I'm about to do.
- Create a certificate for our own personal signing authority
- Create a certificate request for a domain
- Sign the certificate signing request, and generate a signed certificate
- Make a copy of the signed certificate that doesn't need a password when apache starts
sudo mkdir /etc/apache2/ssl
cd /etc/apache2/ssl
1. Generate your own Certificate Authority (CA). Make sure to remember the passphrase you're prompted for. This is what you use to sign certificates.
sudo openssl genrsa -des3 -out ca.key 4096
sudo openssl req -new -x509 -days 1825 -key ca.key -out ca.crt
2. Generate a server key and request for signing (csr). When prompted for the Common Name (CN), enter the domain name you want the certificate for. In my case, the Common Name would be "local.lo-fi.net"
sudo openssl genrsa -des3 -out local.lo-fi.net.key 4096
sudo openssl req -new -key local.lo-fi.net.key -out local.lo-fi.net.csr
3. Sign the certificate signing request with the self-created certificate authority that you made earlier
sudo openssl x509 -req -days 1825 -in local.lo-fi.net.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out local.lo-fi.net.cst
4. Make a key which doesn't cause apache to prompt for a password.
sudo openssl rsa -in local.lo-fi.net.key -out local.lo-fi.net.key.insecure
sudo mv local.lo-fi.net.key local.lo-fi.net.key.secure
sudo mv local.lo-fi.net.key.insecure local.lo-fi.net.key
Repeat steps 2 through 4 for each distinct domain you want to create a certificate for. Another thing that you can do, is name wildcards for your common name. For example, if I wanted a certificate that I could use an all subdomains of lo-fi.net, I can enter a Common Name of "*.lo-fi.net"
Just to review, here's what you should have in your directory
$ ls /etc/apache2/ssl
ca.crt
ca.key
dev.lo-fi.net.crt
dev.lo-fi.net.csr
dev.lo-fi.net.key
dev.lo-fi.net.key.secure
local.lo-fi.net.crt
local.lo-fi.net.csr
local.lo-fi.net.key
local.lo-fi.net.key.secure
Now, you have everything you need for setting up the virtualhost for your domains.
Here's what you've all been waiting for! Configuring multiple virtualhosts with SSL.
Here's the high level review of what we need to do:
- Configure virtualhosting
- Configure mod_ssl
- Add virtualhost configurations for our new hosts.
# Virtual hostsand
Include /private/etc/apache2/extra/httpd-vhosts.conf
# Secure (SSL/TLS) connections
Include /private/etc/apache2/extra/httpd-ssl.conf
Once that is done, you'll need to edit these files somewhat. httpd-vhosts.conf configures virtualhosts running on port 80, designed to apply name-based virtualhosting. I include it here, because I want to test sites on both port 80 and port 443 (https). Using name-based virtualhosting is actually pretty nice for domains when ssl isn't required. All you have to do is create a new <virtualhost> entry, with a "ServerName [whatever.com]" line, and you have a new virtualhost. However, for this file, I'm just going remove the dummy virtualhosts that are in this file as examples, and set a default. Here is what I like to use.
This configuration does 2 things, it names a wildcard virtualhost for port 80, and it defines a virtualhost that will be used for everything other than a name-based virtualhost that we might define later. That does it for the httpd-vhosts.conf file.
#
# Use name-based virtual hosting.
#
NameVirtualHost *:80
#
# VirtualHost example:
# Almost any Apache directive may go into a VirtualHost container.
# The first VirtualHost section is used for all requests that do not
# match a ServerName or ServerAlias in any <VirtualHost> block.
#
<VirtualHost _default_:80>
ServerAdmin eric@mac.com
DocumentRoot "/Library/WebServer/Documents"
ServerName localhost
ErrorLog /private/var/log/apache2/error_log
CustomLog /private/var/log/apache2/access_log common
</VirtualHost>
Next up is the mod_ssl configuration in the httpd-ssl.conf file. The modification for this should be pretty simple. This file does 2 things. First, it sets up the basic configuration for the ssl module. Second, it contains virtualhost configuration for a default virtualhost for port 443. The easiest way to deal with this file is to comment out the default virtualhost configuration (which starts around line 75). This configuration is designed to give you an ssl virtualhost for any host that isn't matched by another specific virtualhost. If this is something you'd like to do, all you have to do is make sure that you have a valid certificate and a key. Look for these lines (line 99 and 107):
SSLCertificateFile "/private/etc/apache2/server.crt"and
SSLCertificateKeyFile "/private/etc/apache2/server.key"If you want to enable this default ssl virtualhost, adjust them as necessary, such that they point to a certificate and key that you have created. OK, now save that file and you're good to go.
The final step is to create some virtualhost files for your domains. I create one per domain, and place them in /etc/apache2/other. Any file with a ".conf" extension is picked up by apache when it starts up. I like to name my files with the domain I'm configuring.
local.lo-fi.net.conf
dev.lo-fi.net.conf
Finally, we can configure the virtualhosts. Here is what mine look like:
local.lo-fi.net.conf
<VirtualHost *:80>
ServerName local.lo-fi.net
DocumentRoot /Users/eric/WebApps/local.lo-fi.net/webroot
ServerAdmin eric@guesswhere.net
<Directory "/Users/eric/WebApps/local.lo-fi.net/webroot">
AllowOverride All
Options
Order allow,deny
Allow from all
</Directory>
</VirtualHost>
#Note the loopback ip address we set up for this host
#local.lo-fi.net = 127.0.0.2
<VirtualHost 127.0.0.2:443>
ServerName local.lo-fi.net
DocumentRoot /Users/eric/WebApps/local.lo-fi.net/webroot
ServerAdmin eric@guesswhere.net
<Directory "/Users/eric/WebApps/local.lo-fi.net/webroot">
AllowOverride All
Options
Order allow,deny
Allow from all
</Directory>
# SSL Configuration
SSLEngine on
SSLCipherSuite ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP
SSLOptions +FakeBasicAuth +ExportCertData +StdEnvVars +StrictRequire
#Self Signed certificates
SSLCertificateFile /etc/apache2/ssl/local.lo-fi.net.crt
SSLCertificateKeyFile /etc/apache2/ssl/local.lo-fi.net.key
SSLCertificateChainFile /etc/apache2/ssl/ca.crt
#DON'T DO ANY INTENSIVE SSL OPERATIONS UNLESS THE FILE IS html OR php
<Files ~ "\.(html|php?)$">
SSLOptions +StdEnvVars
</Files>
SetEnvIf User-Agent ".*MSIE.*" nokeepalive ssl-unclean-shutdown downgrade-1.0 force-response-1.0
</VirtualHost>
dev.lo-fi.net.conf
<VirtualHost *:80>
ServerName dev.lo-fi.net
DocumentRoot /Users/eric/WebApps/dev.lo-fi.net/webroot
ServerAdmin eric@guesswhere.net
<Directory "/Users/eric/WebApps/dev.lo-fi.net/webroot">
AllowOverride All
Options
Order allow,deny
Allow from all
</Directory>
</VirtualHost>
#Note the loopback ip address we set up for this host
#dev.lo-fi.net = 127.0.0.3
<VirtualHost 127.0.0.3:443>
ServerName dev.lo-fi.net
DocumentRoot /Users/eric/WebApps/dev.lo-fi.net/webroot
ServerAdmin eric@guesswhere.net
<Directory "/Users/eric/WebApps/dev.lo-fi.net/webroot">
AllowOverride All
Options
Order allow,deny
Allow from all
</Directory>
# SSL Configuration
SSLEngine on
SSLCipherSuite ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP
SSLOptions +FakeBasicAuth +ExportCertData +StdEnvVars +StrictRequire
#Self Signed certificates
SSLCertificateFile /etc/apache2/ssl/dev.lo-fi.net.crt
SSLCertificateKeyFile /etc/apache2/ssl/dev.lo-fi.net.key
SSLCertificateChainFile /etc/apache2/ssl/ca.crt
#DON'T DO ANY INTENSIVE SSL OPERATIONS UNLESS THE FILE IS html OR php
<Files ~ "\.(html|php?)$">
SSLOptions +StdEnvVars
</Files>
SetEnvIf User-Agent ".*MSIE.*" nokeepalive ssl-unclean-shutdown downgrade-1.0 force-response-1.0
</VirtualHost>
And there you have it. SSL for multiple virtualhosts in Apache for Mac OS X Leopard. Once you restart Apache, you should be up and running, sending the correct Self Signed certificates to the browser.