Configuring strict SNI with Apache httpd 2.4

Imagine the following scenario:

  1. Your web server hosts both www.example.com and www.example.net using Apache httpd with name-based virtual hosts, and the configuration is structured such that httpd sees the former before the latter. Each vhost has its own non-wildcard certificate.
  2. A user tries to access www.example.net using an old browser which does not support SNI.
  3. Since httpd cannot see the Host header without first performing a TLS handshake, and cannot perform a TLS handshake without a key and certificate, it needs to pick one blindly. In accordance with its virtual host matching algorithm, it defaults to the first configured vhost, i.e. www.example.com.
  4. The browser receives a certificate wit a CN which does not match the name of the host it is trying to access, and either throws up a scary warning or simply refuses to connect.

You might say that this acceptable — after all, it only affects a minuscule proportion of users using very old clients — but TLS validators such as Qualys SSL labs have started testing non-SNI requests, and while they don’t yet penalize servers for this issue, they might very well start doing so soon.

Apache httpd has an option called SSLStrictSNIVHostCheck which, in theory, should take care of this. But I haven’t had much luck with it (to put it bluntly: it does not seem to have any effect at all), and it appears from various discussions on Reddit, StackExchange, etc. that I am not alone. The solution I have found is to create a dummy vhost with a dummy, self-signed certificate, and ensure that it is loaded first.

The first step is to create a dummy certificate for localhost with a 100-year lifespan (unless you feel like setting up a cron job that regenerates a 90-day certificate every 30 days or so):

$ openssl genrsa -out localhost.key 2048
$ openssl req -new -key localhost.key -subj /CN=localhost -out localhost.csr
$ echo subjectAltName=DNS:localhost | openssl x509 \
    -in localhost.csr -out localhost.crt -req -signkey localhost.key -days 36525 -extfile -

Next, create a vhost configuration which uses that certificate:

<VirtualHost *:443>
  ServerName localhost
  SSLEngine On
  SSLStrictSNIVHostCheck On
  SSLCertificateKeyFile /path/to/localhost.key
  SSLCertificateFile /path/to/localhost.crt
  <Location />
    Require all denied
  </Location>
</VirtualHost>

This must be placed in a location where it will be read before any other vhost configuration, e.g. 00localhost.conf in your server’s Includes directory.

TLS connections without a server name will now be directed to your dummy vhost and fail, and Qualys will report that any site hosted on your server “works only in browsers with SNI support”.

As a footnote, my TLS configuration (derived from the Mozilla project’s recommendations) looks like this:

SSLProtocol +TLSv1.3 +TLSv1.2
SSLCipherSuite TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
SSLHonorCipherOrder off
SSLCompression off

Between that, strict SNI, HSTS, and CAA, you will easily achieve an A+ score.