HOWTO: Apache Name-based SSL-enabled Virtual Hosting

I want to do virtual hosting of SSL-enabled virtual hosts on the same Apache server as my other non-SSL-enabled virtual hosts. I don't want to assign more than one IP address to the server and all of my virtual hosts will be within the same domain (e.g., example.com).

BACKGROUND

When Apache processes a request for a name-based virtual host it receives the request from the browser, which includes the Host header (e.g., Host: www.example.com). Apache uses the Host header to determine which name-based virtual host to route the request to. It works this way regardless of the connection type, HTTP or HTTPS.

The trouble with SSL-enabled virtual hosting is that HTTPS is simply HTTP traffic tunneled inside of an SSL-enabled TCP connection. This means that everything in the request--including the all-important Host header that Apache needs to correctly route the request to the appropriate virtual host--is not known by Apache until after the SSL handshake takes place. The problem lies in the fact that Apache needs to present the browser with the certificate that corresponds with the virtual host being requested and Apache can only know which certificate to present by determining which virtual host the request is destined for and referring to the configuration directives for the virtual host. It's a classic "Which came first, the chicken or the egg?" problem.

What happens when a browser makes an HTTPS request to a name-based virtual host is that Apache responds by presenting the certificate for the first SSL-enabled virtual host. Technically, Apache responds to the initial SSL request by applying the configuration for the default virtual host listening on port 443.

For instance, let's imagine that we have an Apache server set up to do name-based virtual hosting of two SSL-enabled virtual hosts on port 443. The first virtual host has the ServerName www.example.com and the second virtual host has the ServerName www2.example.com. If the virtual host for www.example.com appears first in the apache configuration then it will be the default virtual host for port 443. As a result, any client that makes a request for https://www2.example.com will get presented with the certificate for www.example.com. Of course the Web browser will not like this and present an error message to the user stating that the certificate presented does not correspond with the request. If the user was to click through the error message the request would actually be routed to the correct virtual host, www2.example.com.

The real problem lies in this error that the user is presented with. We don't want to be training users to ignore SSL/TLS errors.

SOLUTION

As long as the virtual hosts you want to provide share the same root domain the way to tackle this issue is to configure both virtual hosts with the same certificate, but not just any certificate, a wildcard certificate. Usually you would get two certificates, one for each virtual host. The common name for the first certificate would be www.example.com and the second one would be www2.example.com. A wildcard certificate has a common name of the form *.example.com, which means it will match any hostname in the example.com domain, including www.example.com and www2.example.com. If you want to limit the scope of the wildcard certificate you could get a certificate with the common name www*.example.com which would limit it to hosts that begin with www in the example.com domain.

EXAMPLE CONFIGURATION

NOTE: Both virtual hosts refer to the same certificate and key files.


...
Listen 443
...
...SSL stuff...

<VirtualHost _default_:443>
ServerName www.example.com:443
...VirtualHost stuff...
SSLEngine on
SSLCertificateFile /path/to/*.example.com.crt
SSLCertificateKeyFile /path/to/*.example.com.key
...VirtualHost stuff...
</VirtualHost>

<VirtualHost *:443>
ServerName www2.example.com:443
...VirtualHost stuff...
SSLEngine on
SSLCertificateFile /path/to/*.example.com.crt
SSLCertificateKeyFile /path/to/*.example.com.key
...VirtualHost stuff...
</VirtualHost>