Redirect to SSL only if browser supports SNI

I have Apache 2.2 with mod_ssl and a bunch of sites in HTTPS on the same IP/port with VirtualHosting, so client must support SNI to connect to those virtual hosts.

I would like to configure my server the following way:

When a user types www.dummysite.com and his browser supports SNI (Server Name Indication), any HTTP request is redirected to https:// where an HSTS header is sent. But if the browser doesn't support SNI then the request is served by HTTP.

The above rule, stated as is, is actually a fallback rule for those people that still run old browsers, as Mozilla and Chrome don't have this problem, just to avoid leaving these users out of the site.

I would like to do this redirecting at the Apache config level, perhaps with a filter on the user agent. I wouldn't like to touch running applications except making sure that no direct http:// references are present (otherwise they imply a security warning)

[Edit] (while editing the question I forgot the question): what is the list of SNI-enabled user agents to redirect?


Since SNI occurs during the SSL/TLS handshake, it's not possible to detect browser support when the client connects to HTTP.

So, you're right; a user-agent filter is the only way to do this.

The big question is whether you want to act on a blacklist against browsers that you know won't listen for SNI, or a whitelist of browsers that are known to support it. Obscure or new devices being unable to use the site seems like a deal-breaker, so I'd say the whitelist might be the better option.

In your HTTP <VirtualHost>:

# Internet Explorer 7, 8, 9, on Vista or newer
RewriteCond %{HTTP_USER_AGENT} MSIE\s7.*Windows\sNT\s6 [OR]
RewriteCond %{HTTP_USER_AGENT} MSIE\s8.*Windows\sNT\s6 [OR]
RewriteCond %{HTTP_USER_AGENT} MSIE\s9.*Windows\sNT\s6 [OR]
# Chrome on Windows, Mac, Linux
RewriteCond %{HTTP_USER_AGENT} Windows\sNT\s6.*Chrome [OR]
RewriteCond %{HTTP_USER_AGENT} Macintosh.*Chrome [OR]
RewriteCond %{HTTP_USER_AGENT} Linux.*Chrome [OR]
# Firefox - we'll just make the assumption that all versions in the wild support:
RewriteCond %{HTTP_USER_AGENT} Gecko.*Firefox
RewriteRule ^/(.*)$ https://ssl.hostname/$1 [R=301]

Here's the blacklist option, too - keep in mind that this runs the risk of sending a client that doesn't use SNI to an SNI-needed site, but on the other hand, will send users of something new like IE 10 to the right place:

# IE 6
RewriteCond %{HTTP_USER_AGENT} !MSIE\s6
# Windows XP/2003
RewriteCond %{HTTP_USER_AGENT} !Windows\sNT\s5
# etc etc
RewriteRule ^/(.*)$ https://ssl.hostname/$1 [R=301]

There are a lot of browsers out there. I've been pretty loose with the expressions and haven't covered a lot of browsers - this could turn into quite the nightmare to maintain.

Whichever option you choose.. good luck!


My solution is this:

  # Test if SNI will work and if not redirect to too old browser page
  RewriteCond %{HTTPS} on
  RewriteCond %{SSL:SSL_TLS_SNI} =""
  RewriteRule ^ http://www.example.com/too-old-browser [L,R=307]

If an old browser without SNI tries to access https://www.example.com/* then it will throw a error on the browser first, which cannot be avoided since until apache replies to a non-SNI browser it doesn't know which site it is asking for. Then it redirects to a page telling the user their browser is too old (as long as user clicks continue to website).

And for users with new browsers I have

  #Test if new browser and if so redirect to https
  #new browser is not MSIE 5-8, not Android 0-3
  RewriteCond %{HTTPS} off
  RewriteCond %{HTTP_USER_AGENT} !MSIE\ [5-8]
  RewriteCond %{HTTP_USER_AGENT} !Android.*(Mobile)?\ [0-3]
  RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]

That excludes most of the old browsers, including some like MSIE 5-8 on Vista (9+ is only Vista/7 so supports SNI). It's not 100% (symbian is ignored etc.) but should work for the majority. The minority can still choose to accept the certificate error.


As far as I'm aware there's not really a good way to do this -- You can use a mod_rewrite rule or similar conditionally based on the User-agent header, but it would have to be on a NON-SSL vhost: If the browser does not support SNI and it goes to a secure (https://) site it's going to get the old-school Apache behavior of "Here's the first SSL certificate I have associated with that IP address -- Hope it's what you wanted!" -- If that's not the certificate the browser was expecting you'll wind up with an error message about hostname mismatches.

This basically means people have to hit a non-SSL interstitial page that will redirect them - possibly exposing any data they're sending in their request. This may or may not be a deal breaker (you say you're going to send them to a non-SSL site anyway if they don't support SNI, so I assume you don't care that much about security. If I were designing a system that had a need for SSL as an encryption or authentication layer I'd be a bit more insistent about it though...)

None of that stops someone from bookmarking the secure site though - and if they use a shared bookmark service or restore their bookmarks to a machine where the web browser doesn't support SNI they're back in the Potential-for-SSL-Errors case.


I'd be tempted to solve this one of three ways:

  1. RewriteRule based on User-Agent headers.
  2. Load a https:// URI in a <SCRIPT> tag on a non-default VHost; if the load succeeds, it's a bit of JS that reloads the whole page under HTTPS.
  3. Teach my visitors to use something like HTTPS Everywhere if this is a priority for them, force HTTPS on pages where it should be required, and hope everything works out in the end.

Of these, I personally like #2 the best, but that involves modifying your sites' code.