Skip to content
 

Name-based SSL virtual hosts in Apache

Traditionally, the SSL protocol hasn't allowed the implementation of name-based virtual hosting with Apache. The problem is quite simple: for SSL (or TLS, which is what it is nowadays usually), Apache should present an SSL certificate to the client; if there are many SSL virtual hosts, Apache needs to know which one was requested by the client (to select the proper certificate); to do that, it reads the HOST header sent by the client in the HTTP dialog. But the SSL session should be established before the HTTP dialog even begins, so we have a chicken-and-egg problem here.

There are a few ways around this problem. Each one has issues. We'll have a look at each of these here.

Some background

HTTP over TLS is defined by the informational RFC 2818. That RFC contains an important part regarding the methods used for endpoint identification, ie how the client and the server identify each other. The important part for the discussion about virtual hosts is how the server identifies to the client. This is what it says: "If a subjectAltName extension of type dNSName is present, that MUST be used as the identity. Otherwise, the (most specific) Common Name field in the Subject field of the certificate MUST be used. Although the use of the Common Name is existing practice, it is deprecated and Certification Authorities are encouraged to use the dNSName instead". This is very important: despite the overwhelming majority of websites still using the CN (Common Name), that is deprecated, and subjectAltName should be used instead. In practice, most CA (Certification Authorities) nowadays put the information in both places. However, it is valid to only use subjectAltName.
We will refer to the RFC when appropriate in the rest of the post.

Multiple CN

This has probably been one of the first tricks people used to get around the limitations of SSL. Essentially, just put multiple Common Names (CN) in the certificate's subject, ie like

CN=www.example.com/CN=dev.example.org/CN=someothersite.somedomain.tld

Some considerations:

  • As we saw, the use of CN is deprecated. Nonetheless, it still seems to be used by the majority of sites out there.
  • Not all browsers support multiple CNs; some (like Firefox) will just pick the first one as the server's name, and if the connection happens to be to one of the other names, a name mismatch warning is issued.
  • From what I can see, not all the CAs will sign a certificate with multiple CN (but probably for mere money reasons, not for technical reasons).
  • All the CNs must be listed and thus exposed to whoever happens to inspect the certificate. This might not be desirable, especially if you discover that www.yourbank.com is listed along with www.p0rnwarez.com.

Wildcards

Another widely used trick is to use a wildcard certificate, ie a certificate that covers any name ending in .example.com (for example). In other words, a certificate for *.example.com.

Some considerations on wildcard cetificates:

  • Again, the wildcard string is (usually) listed in the CN. That is deprecated, but still the majority of sites use it. Putting the wildcard string in a subjectAltName would probably be more appropriate. With some random sample of Internet sites using wildcards, it seems that some use only CN, some use both CN and subjectAltName (perhaps with some additional name listed in the subjectAltName, like eg *.example.com, www.example.com, example.com).
  • According to the RFC, all the names covered by the wildcard certificate must be at the first level under the domain, so for example www.example.com and dev.example.com are ok, but www.sales.example.com is not. This is the official version: "Names may contain the wildcard character * which is considered to match any single domain name component or component fragment. E.g., *.a.com matches foo.a.com but not bar.foo.a.com. f*.com matches foo.com but not bar.com.". See also this page for a more detailed discussion.
  • Obviously, with a single wildcard you're restricted to having all the virtual hosts under the same domain. However, this can probably be combined with subjectAltName (see below), so adding names or wildcards in other domain can be possible.
  • A wildcard certificate usually costs a lot. If you need it for three or four names, it may be that buying three or four individual certificates is cheaper. Furthermore, CAs usually have licences on the certificates that restrict their use. Typically, the licence allows to use one certificate on a single server. To use it on more servers (which is a likely reason one might want a wildcard certificate in the first place!), additional licences must be purchased. Again, these licences are not free (rather, they usually cost as much as the certificate).

Subject Alternative Name (SAN)

Subject Alternative Name (or subjectAltName, or SAN for short) is one of a number of X.509v3 extensions that can be included in a certificate. It allows to specify additional information like email addresses, IP numbers, and (more interesting) DNS names. As we saw, the use of subjectAltName is the preferred way the client must use for server identification, and thus it should be used in preference to the CN (though as I said, in practice the use of CN still seems to be prevalent; some CAs put the same names as CN and SANs, just in case). Here's a certificate that includes SANs:

subjectAltName

Some considerations on SANs:

  • SAN certificates are usually more expensive than normal certificates (though surely cheaper than wildcard certificates). Usually, CAs charge per-SAN, so if you're going to add a lot of names, you should consider that. And the same licensing issues (usually) apply as well.
  • As with the multiple CN case, exposing all the names cannot be avoided.
  • Since the RFC says that if a subjectAltName is present that MUST be used to identify the server, putting a name in the CN and other names as SAN might not work with strictly RFC-compliant browsers (like Firefox), in that they might accept only the names listed as SANs; so you better put all the names as SANs, regardless of whether or how many you put in the CN.
  • It's definitely possible, and some certificates used by some websites confirm that, to use one or more wildcard names like *.example.com as SANs; this will probably cost quite a lot of money, but the sites that can do that can probably afford it.
  • Once a certificate containing SANs is issued, it's not possible to remove or add names; a new certificate must be issued.

Apache configuration for the previous methods

Configuring Apache to support multiple SSL virtual hosts using one of the previous techniques is quite simple. Since only one certificate is involved, just configure each virtual host to use the same certificate:

Listen 80
# if you do non-SSL virtual hosts
NameVirtualHost *:80

<IfModule mod_ssl.c>
    Listen 443
    # Yes, this shouldn't be here, but ...
    NameVirtualHost *:443
</IfModule>
...
<IfModule mod_ssl.c>
<VirtualHost *:443>
        DocumentRoot /var/www/www.example.com
        ServerName   www.example.com
        ErrorLog /var/log/apache2/www.example.com-error.log
        CustomLog /var/log/apache2/www.example.com-ssl_access.log combined
        SSLEngine on
        SSLCertificateFile /etc/ssl/certs/yourcert.pem
        SSLCertificateKeyFile /etc/ssl/private/yourkey.key
</VirtualHost>

<VirtualHost *:443>
        DocumentRoot /var/www/dev.example.org
        ServerName   dev.example.org
        ErrorLog /var/log/apache2/dev.example.org-error.log
        CustomLog /var/log/apache2/dev.example.org-ssl_access.log combined
        SSLEngine on
        SSLCertificateFile /etc/ssl/certs/yourcert.pem
        SSLCertificateKeyFile /etc/ssl/private/yourkey.key
</VirtualHost>
...
# other virtual hosts here ....
</IfModule>

Where, of course, /etc/ssl/certs/yourcert.pem will be your wildcard/SAN/whatever certificate (in this case, a SAN certificate, since the domains are different).

In practice, Apache will probably use the certificate defined in the virtual host that comes first in the configuration, but it doesn't hurt to be explicit even if it's redundant. UPDATE: removing all the SSL directives from all the virtual hosts but the first seems indeed to work fine. I still prefer to be expicit though.

Note that apache will surely complain at startup, eg something like this:

[Wed Dec 15 22:32:46 2009] [warn] Init: SSL server IP/port conflict: dev.example.org:443 (/etc/apache2/sites-enabled/dev.example.org:2) vs. www.example.com:443 (/etc/apache2/sites-enabled/www.example.com:2)
[Wed Dec 15 22:32:46 2009] [warn] Init: You should not use name-based virtual hosts in conjunction with SSL!!

but then it will work fine, since ultimately the certificate that it will serve to clients will always be the same, and Apache's name-based virtual host selection will still work (to get ServerName, DocumentRoot, etc.) once the SSL connection has been established.

Server Name Indication

All the previous techniques are somehow kludgy (except maybe the SAN), because they still require that everything is done with a single certificate (and thus a single responsible organization). This is not very flexible, and it is also impractical when a web server hosts hundreds or thousands of SSL websites; it would be nice to be able to have truly distinct certificates, one per virtual host. Here's where Server Name Indication (or SNI for short) comes into play. SNI is an extension to the TLS protocol (see the RFC), that allows the client to send the requested server name in cleartext at the beginning, as part of the initial SSL/TLS negotiation (in the ClientHello message, in case you're curious). This way, the server can learn which site the client is requesting, and serve the appropriate certificate. This is by far the cleanest way to do SSL virtual hosting, since it allows to have many different certificates, each of which could potentially be managed by a different organization or individual.

To work, SNI must be supported by both the client (because it sends the server's name when it starts the SSL negitiation) and the server (because it must know how to look for the requested name in the incoming request and present the right certificate for it). Fortunately, these days the majority of the most popular browsers support SNI, and recent versions of OpenSSL (since 0.9.8f) and Apache (since 2.2.12, finally) support it as well.

To do SNI, Apache is configured exactly in the same way as before, except a different certificate (and key) is specified for each virtual host:

Listen 80
# if you do non-SSL virtual hosts
NameVirtualHost *:80

<IfModule mod_ssl.c>
    Listen 443
    # Now this has the full right to be here ...
    NameVirtualHost *:443
</IfModule>
...
<IfModule mod_ssl.c>
<VirtualHost *:443>
        DocumentRoot /var/www/www.example.com
        ServerName   www.example.com
        ErrorLog /var/log/apache2/www.example.com-error.log
        CustomLog /var/log/apache2/www.example.com-ssl_access.log combined
        SSLEngine on
        SSLCertificateFile /etc/ssl/certs/www.example.com-cert.pem
        SSLCertificateKeyFile /etc/ssl/private/www.example.com-key.key
</VirtualHost>

<VirtualHost *:443>
        DocumentRoot /var/www/dev.example.org
        ServerName   dev.example.org
        ErrorLog /var/log/apache2/dev.example.org-error.log
        CustomLog /var/log/apache2/dev.example.org-ssl_access.log combined
        SSLEngine on
        SSLCertificateFile /etc/ssl/certs/dev.example.org-cert.pem
        SSLCertificateKeyFile /etc/ssl/private/dev.example.org-key.key
</VirtualHost>
...
# other virtual hosts here ....
</IfModule>

And when Apache starts up you should see this (more reassuring) message in the log:

[Thu Dec 16 21:16:19 2009] [warn] Init: Name-based SSL virtual hosts only work for clients with TLS server name indication support (RFC 4366)

This is how a Client Hello as sent by Firefox looks like, including the SNI:

SNI

Some considerations about SNI:

  • While support is improving, it might not be widespread yet; in particular, it seems that Internet Explorer supports it only in IE7, and only from Vista; there are still a lot of IE6 and Windows XPs out there. Then, SNI is a TLS extension (and maybe SSLv3); surely there are still a lot of clients that only do SSLv2, what about them?
  • On the server side, it's unusual to be running the latest and greatest software in production setups; this applies to Apache as well, of course. Apache prior to 2.2.12 can do SNI but it needs to use mod_gnutls rather than mod_ssl, which introduces all the usual GnuTLS annoyances.
  • If possible, go with SNI over any other option; it's definitely the right way to do SSL virtual hosting.

See this page on the Apache wiki for more information about SNI.

See this page on the CAcert wiki for a good discussion of SSL virtual hosting, and some considerations about the deprecated CN usage.

9 Comments

  1. dogglebones says:

    Below is a shell script (bash) that, combined with my commentary, will hopefully save you all a good bit of digging. To say the least, this stuff is quite cryptic (ha!).

    Here's what the VHOST + SSL situation boils down to: You can use "SNI" certificates which are not widely supported client-side, or use "SAN" certificates which are secure but you lose authenticity (to some degree), or hack your web server to dedicate a certain listening IP or port to a certain certificate. The shell script assumes you want that middle option.

    There is currently no way to setup Apache to use VirtualDocumentRoot in conjunction with SSL certificates. The folks at http://www.outoforder.cc who make mod_gnutls also make a thing called mod_vhost_dbi. Here's a snippet from their documentation page, modified slightly to enable SSL stuff:

    VhostDbiEnabled On
    VhostDbiConnName Server1
    [SSLEngine on]
    VhostDbiQuery "SELECT ServerName, DocumentRoot, Username[, SSLCertificateFile, SSLCertificateKeyFile]" FROM vhost_info WHERE ServerName = "

    Sexy trick, sure, but frankly I'd rather modify my s by hand than integrate some stinking SQL database. Nevertheless it seems to be an option.

    The way I've got my server running, HTTP port 80 uses VirtualDocumentRoot and there's no site-specific configuration. HTTPS port 443 uses a container for each site, in the following way:

    DocumentRoot /var/www/domain.tld
    ServerName http://www.domain.tld
    ServerAlias domain.tld *.domain.tld
    SSLEngine On
    SSLCertificateFile /var/www/domain.tld/turkishkitchenaz.com.crt
    SSLCertificateKeyFile /var/www/domain.tld/turkishkitchenaz.com.key

    As for the certificates themselves, you may want them self-signed and you may want to use a "trusted" certificate authority (so you can get one of those coveted green address bars perhaps). Because you have to use VirtualHost containers for each site you're serving, you can use domain-specific certificates, but I assume you will want the certificate to work with any sub-domain too (like my ServerAlias directive above might).

    --- mkcert.sh ---

    #!/bin/bash

    clear

    path="$HOME/Desktop"
    days=""

    echo -ne "Hello. You must be hoping to create a SSL/TLS security certificate (and key).\n\nBy default, this key will be self-signed. However, you can decide instead to\ncreate an un-signed certificate request (.pem) file to be signed by a\nrecognized certificate authority.\n\nPlease choose from the following options.\n\n s: create a self-signed certificate .crt file (default)\n u: create an un-signed certificate request .pem file\n q: quit\n> "
    read go

    if [ "$go" = "u" ]; then

    echo -ne "What is your country code?\n> "
    read c
    echo -ne "What is your state/province?\n> "
    read st
    echo -ne "What is your city?\n> "
    read l
    echo -ne "What is your organization name?\n> "
    read o

    elif [ "$go" = "q" ]; then

    echo -e "\nHave a nice day."
    exit 0

    fi

    echo -ne "What is your domain name?\n> "
    read domain

    if [ -z "$domain" ]; then
    echo "No domain was given."
    exit 0
    fi

    echo -ne "What is the path where your certificate files should be saved?\n> "
    read path
    echo -ne "For how many days will your certificate be valid?\n> "
    read days

    if [ -z "$path" ]; then path="."; fi
    if [ -z "$c" ]; then c="US"; fi
    if [ -z "$st" ]; then st="Arizona"; fi
    if [ -z "$l" ]; then l="Phoenix"; fi
    if [ -z "$o" ]; then o="PLUG"; fi
    if [ -z "$days" ]; then days="365"; fi

    if [ -d "$path" ]; then
    echo -ne "\nCreating your certificate ..."
    if [ "$go" = "u" ]; then
    (echo -e "oid_section = new_oids\n[ new_oids ]\n[ req ]\ndefault_days = $days\ndefault_keyfile = $path/$domain.key\ndistinguished_name = req_distinguished_name\nencrypt_key = no\nstring_mask = nombstr\nreq_extensions = v3_req\n[ req_distinguished_name ]\ncommonName = Common Name (eg, YOUR name)\ncommonName_default = $domain\ncommonName_max = 64\n[ v3_req ]\nsubjectAltName=DNS:$domain,DNS:*.$domain" > deleteme.cfg) &> /dev/null;
    (openssl req -batch -config deleteme.cfg -newkey rsa:2048 -out $path/$domain.pem) &> /dev/null;
    (rm deleteme.cfg) &> /dev/null;
    else
    (echo -e "subjectAltName=DNS:$domain,DNS:*.$domain" > deleteme.cfg) &> /dev/null;
    (openssl genrsa -out $path/$domain.key 2048) &> /dev/null;
    (openssl req -new -key $path/$domain.key -subj "/C=$c/ST=$st/L=$l/O=$o/CN=$domain" -out deleteme.csr) &> /dev/null;
    (openssl x509 -req -in deleteme.csr -signkey $path/$domain.key -days $days -text -extfile deleteme.cfg -out $path/$domain.crt) &> /dev/null;
    (rm deleteme.cfg deleteme.csr) &> /dev/null;
    fi
    (chmod 400 $path/$domain.key) &> /dev/null;
    echo " done."
    else
    echo "No such directory exists."
    fi

    echo -e "\nHave a nice day."

    • waldner says:

      I'm not sure what's the point you're trying to make here. There's no such thing as "SNI certificates", SNI is an extension of the SSL protocol and yes, it should be widely supported by browsers these days. However, since I'm not a big fan of mod_gnutls (admittedly, because I haven't had the chance to play with it than anything else), I agree it would be nice if VirtualDocumentRoot could be used with SSL virtual hosts, perhaps using something like VirtualSSLCertificateFile and the like supporting template patterns.
      As a side note, beware that a wildcard certificate for *.domain.tld only covers abc.domain.tld and not abc.def.domain.tld (that is, only one level below).

      • dogglebones says:

        You're right, there's no such thing as "SNI certificates": if a browser supports it, you'll get an indication of the hostname being requested along with the SSL connection request, and if a browser does not, you wont. So relying on it is not a good idea. The point of my post was to let people know that the SAN option is really the only way to go if you must virtualhost HTTPS, to provide a shell script that does the legwork of making *.domain.tld (and not necessarily *.*.domain.tld) self-signed certificates and/or certificate "requests" that can be signed by a paid authority, and to provide a way to configure a web server so that it -will- work with any number of virtualhosts and with any browser. (Note that, while my post has been slightly mangled, the shell script is intact and will work as intended.)

  2. gus says:

    great brief. thanks. obviously jumping into new changes as soon as they emerge cause conformance issues with the genpub. being this is mid 2010, and being that 2048bit is required by end of 2010, now is a good time to make the grade. my recent reviews have indicated that MOD_GNUTLS is the best/simple/easy way to go. here is the quick compare we used... http://www.gnu.org/software/gnutls/comparison.html. never mind that system interfaces may not work with it - dont know. one thing is for sure - its getting really expensive and maybe camellia will break that trend. http://info.isl.ntt.co.jp/crypt/eng/info/chiteki.html.

  3. "Since the RFC says that if a subjectAltName is present that MUST be used to identify the server, putting a name in the CN and other names as SAN might not work with strictly RFC-compliant browsers (like Firefox)"
    Davide, hi.
    Can you refer me to the section of the RFC that requires that?

    RFC (2459) says "subject alternative names extension allows *additional* identities to be bound to the subject of the certificate", and the text seems to imply that the SAN is used in addition to the CN and not instead.

    Thanks!

    • waldner says:

      As I read it, it's in RFC 2818, section 3.1, "Server identity". Speaking about how the client should identify the server, it says

      ...If a subjectAltName extension of type dNSName is present,
      that MUST be used as the identity. Otherwise, the (most specific)
      Common Name field in the Subject field of the certificate MUST be used.
      Although the use of the Common Name is existing practice, it is deprecated
      and Certification Authorities are encouraged to use the dNSName instead.

      (the emphasis is mine)

      Granted, the RFC is only informational and a bit dated, but some other sources on the Internet and some empirical tests with Firefox and some home-made certificates seem indeed to confirm that if the SAN is present,the CN is ignored. Of course, other browsers might or might not do the same.
      It seems there's a bit of confusion on the matter. What I've seen after examining several SSL certificates received from various random Internet websites is that either there's no SAN (with the domain name in the CN - traditional style), or if SAN(s) are present, to play safe, the list of SAN domains invariably includes the domain in the CN (plus others, maybe). See for example the certificates you get from https://www.bluetooth.org, https://launchpad.net/, https://acrobat.com/, https://www.scientificlinux.org/, or even https://proxify.co.uk for an extreme example. (just to name the first sites that come up with a quick google search)
      I haven't seen different arrangements so far, but of course additional information is always welcome.

      BTW, RFC 2459 has been obsoleted by RFC 3280 and then by 5280, which in section 4.2.1.6, Subject Alternative Name says:

      ...The subject alternative name extension allows identities to
      be bound to the subject of the certificate. These identities may be included
      in addition to or in place of the identity in the subject field of
      the certificate.

      Note the slightly different phrasing, the word "additional" is not where it used to be.

      Thanks!

      • Thanks for the detailed reply.
        Nice to see the 2818 memo, I was not acquainted with it.

        And yes, empiric trials with firefox (and IE too) showed the behavior mentioned in the post, but I was not sure they had official RFC support.

        Thanks a lot!