Serving Secure Sites With SNI On Apache


What is SNI

SNI (Server Name Indication) is an extension to SSL that allows multiple SSL-enabled Web sites to be served from a single IP address and port (443). While it requires visitors to use more recent browser versions, it helps get around the problem of requiring separate IP addresses for every secure site hosted on the same Web server.

For our example we'll set up two sites with SSL: secure1.example.com and secure2.example.com. Both sites will be served by the same IP address. We'll use separate SSL certificates for each site.

We'll also set up an unsecured site, www.example.com, for contrast and testing purposes. This site will be served from the same IP address as the two SSL-enabled sites but use port 80 instead of 443.

Pre-requisites

We'll use the Apache Web server with mod_ssl and OpenSSL for this article.

If you are using Ubuntu 10.04 (or newer) or Fedora 10 (or newer) on your server the Apache and OpenSSL packages that ship with these distributions support SNI already. If you are using Red Hat Enterprise Linux / CentOS 5.x or Debian 5.x you may need to compile Apache and OpenSSL yourself.

If you're compiling Apache yourself, note that SNI is supported in Apache versions 2.2.12 and newer. You'll also need OpenSSL 0.9.8f or newer for SNI (specifically, the "TLS extensions" that enable SNI). You can find more instructions in the Apache SNI wiki entry.

Browsers

SNI-capable browsers are required on the client's side to access the sites securely. The Wikipedia article on SNI has a current list of browsers that support SNI.

In particular, note that Internet Explorer on Windows XP does not support SNI. Recent versions of IE on Windows Vista and Windows 7 do support SNI, as do recent versions of Firefox, Chrome, and Safari.

To see if your browser supports making secure connections to an SNI-enabled server you can visit this test site:

https://alice.sni.velox.ch/

How-to

We'll assume that you are familiar enough with Apache to be able to find its configuration files and set up a virtual host. If not, visit our article repository for some tutorials on installing and configuring Apache.

Apache set-up

Make sure mod_ssl is enabled in your Apache installation, either by uncommenting the necessary lines in httpd.conf or using "a2enmod" on Ubuntu or Debian. Most package installations of Apache will have mod_ssl enabled by default.

You'll also want to make sure that Apache will listen to port 443 (for https connections) and use name-based virtual hosts on that port.

There should be an "IfModule mod_ssl.c" block in the main httpd.conf file, the ports.conf file, or in a mod_ssl-specific config file (depending on how Apache is set up). Inside that block you'll find the line:

Listen 443

In the same mod_ssl.c block let's also add the line:

NameVirtualHost *:443

That's really the only SNI-specific bit of configuration we'll need to do - telling Apache that it should use named virtual hosts on the secure port. The other steps we'll walk through are the same things you'd do when setting up any secure Web site.

NOTE: Since we are going to set up a virtual host on port 80 too (that is, www.example.com), there should also be a line "NameVirtualHost *:80" elsewhere in your Apache configuration. On Ubuntu / Debian look in ports.conf, and on most other distributions look in httpd.conf. If "NameVirtualHost *:80" is commented out, uncomment it.

Virtual hosts

Now we'll set up the virtual hosts.

For this example we'll keep our sites in separate subdirectories of the "demo" user's home directories. We start by making the directories for the domains:

cd /home/demo
mkdir -p public_html/www.example.com/{public,private,log,cgi-bin,backup}
mkdir -p public_html/secure1.example.com/{public,private,log,cgi-bin,backup}
mkdir -p public_html/secure2.example.com/{public,private,log,cgi-bin,backup}

Next we'll head over to the Apache configuration directory and set up our virtual host configurations.

Let's look at the config for our unsecured site:

<VirtualHost *:80>
	ServerName "www.example.com"
	ServerAdmin webmaster@example.com
	DocumentRoot/home/demo/public_html/www.example.com/public
	ErrorLog /home/demo/public_html/www.example.com/log/error.log
	LogLevel warn
	CustomLog /home/demo/public_html/www.example.com/log/access.log combined
	<Directory /home/demo/public_html/www.example.com/public>
		Options Indexes FollowSymLinks MultiViews
		AllowOverride None
		Order allow,deny
		allow from all
	</Directory>
</VirtualHost>

That configuration will tell Apache that the Web site www.example.com will be served over port 80.

Next we'll make a config for the first of our secure sites, secure1.example.com. The config looks similar to our "regular" virtual host, but secure1.example.com will be served over port 443 and include SSL configuration lines at the end:

<VirtualHost *:443>
	ServerName "secure1.example.com"
	ServerAdmin webmaster@example.com
	DocumentRoot/home/demo/public_html/secure1.example.com/public
	ErrorLog /home/demo/public_html/secure1.example.com/log/error.log
	LogLevel warn
	CustomLog /home/demo/public_html/secure1.example.com/log/access.log combined
	<Directory /home/demo/public_html/secure1.example.com/public>
		Options Indexes FollowSymLinks MultiViews
		AllowOverride None
		Order allow,deny
		allow from all
	</Directory>
	SSLEngine On
	SSLCertificateFile /var/www/certs/secure1.pem
	SSLCertificateKeyFile /var/www/keys/secure1.key
</VirtualHost>

Note that the certificate file is secure1.pem and the key file is secure1.key.

Similarly, the VirtualHost file for secure2.example.com would be:

<VirtualHost *:443>
	ServerName "secure2.example.com"
	ServerAdmin webmaster@example.com
	DocumentRoot/home/demo/public_html/secure1.example.com/public
	ErrorLog /home/demo/public_html/secure2.example.com/log/error.log
	LogLevel warn
	CustomLog /home/demo/public_html/secure2.example.com/log/access.log combined
	<Directory /home/demo/public_html/secure2.example.com/public>
		Options Indexes FollowSymLinks MultiViews
		AllowOverride None
		Order allow,deny
		allow from all
	</Directory>
	SSLEngine On
	SSLCertificateFile /var/www/certs/secure2.pem
	SSLCertificateKeyFile /var/www/keys/secure2.key
</VirtualHost>

We use different certificate and key files for secure2.example.com, secure2.pem and secure2.key.

With this set-up secure2.example.com is being served on port 443, just like secure1.example.com. If we have a single public IP address on our server that means both secure sites are sharing the same IP address and port. On a non-SNI-based set-up this configuration would not work!

Test pages

To finish the set-up off let's make some index pages for each site to help us test.

Create the index page for the unsecured site at:

/home/demo/public_html/www.example.com/public/index.html

And inside the file we'll put:

<html>
<head><title>WWW works</title></head>
<body>
<p>
WWW.example.com works
</p>
</body>
</html>

The page for secure1.example.com would be located at:

/home/demo/public_html/secure1.example.com/public/index.html

And the index page would be:

<html>
<head><title>SECURE1 works</title></head>
<body>
<p>
SECURE1.example.com works
</p>
</body>
</html>

And finally, for secure2.example.com, the index page location would be:

/home/demo/public_html/secure2.example.com/public/index.html

The file would contain:

<html>
<head><title>SECURE2 works</title></head>
<body>
<p>
SECURE2.example.com works
</p>
</body>
</html>

Now we have both sites set up with their own document roots and certificates.

Enable our Web sites

Enable the Web sites by making sure the virtual host configurations are in the right place and enabling them if needed (use "a2ensite" on Ubuntu or Debian, for example).

Restart Apache

With all that done you should restart Apache to make the configuration changes stick.

Once it's up you can make sure Apache is listening for both secure and normal connections with the netstat command. The "grep" at the end of the command looks for the process name - on some distributions that's "apache" or "apache2", on others it would be "httpd":

sudo netstat -tnlp | grep apache
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      9965/apache2    
tcp        0      0 0.0.0.0:443             0.0.0.0:*               LISTEN      9965/apache2    

If the Web server is listening only on port 80 and not on port 443, double-check your config to make sure mod_ssl is installed and enabled.

All done!

 

If all went well you should be able to visit your unsecure site, as a control:

http://www.example.com

If you see "WWW.example.com works" then you know Apache is up and running.

Now visit both secure sites in an SNI-enabled browser to make sure you get secure connections:

https://secure1.example.com
https://secure2.example.com

Check that your client indicates a secure connection (usually a "lock" symbol) and that the test index page is displayed. You can also check the certificate properties in your browser to make sure the two sites are using their respective SSL certificates.

Towards a better set-up

This set-up was done with an eye toward sharing a single IP address for all sites. To keep things cleaner you could use two IP addresses, one for all your regular Web sites over http, and the other IP address for all your SSL-enabled Web sites.

Let us say we have 1.2.3.253 and 1.2.3.252 assigned to the same server, and that we use 1.2.3.253 for regular http and 1.2.3.252 for https. In that case the changes to be made would be as follows:

  • Change "NameVirtualHost *:80" to "NameVirtualHost 1.2.3.253:80"
  • [Optional] Change "Listen 80" to "Listen 1.2.3.253:80"
  • Change "NameVirtualHost *:443" (in the mod_ssl.c section) to "NameVirtualHost 1.2.3.252:443"
  • [Optional] Change "Listen 443" (in the mod_ssl.c section) to "Listen 1.2.3.252:443"

In the configuration for the default/non-SSL site:

  • Change "<VirtualHost *:80>" to "<VirtualHost 1.2.3.253:80>"

And in each secure virtual host config:

  • Change "<VirtualHost *:443>" to "<VirtualHost 1.2.3.252:443>"

Then restart Apache to implement the changes.

Don't forget to change the DNS too!

  • SSL-enabled Web-site host-names must resolve to 1.2.3.252
  • Regular Web-site host-names must resolve to 1.2.3.253

Summary

Now you should have multiple secure sites served from the same IP address. Excellent work.

If you'd like more information about implementing SNI you can visit the Apache wiki page on SNI. If you want specific details you can also read RFC 4366, which describes SNI and other TLS extensions.

Raghu V. Rao



Was this content helpful?




© 2014 Rackspace US, Inc.

Except where otherwise noted, content on this site is licensed under a Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported License


See license specifics and DISCLAIMER