Take the 2-minute tour ×
Server Fault is a question and answer site for system and network administrators. It's 100% free, no registration required.

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?

share|improve this question

6 Answers 6

up vote 11 down vote accepted

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!

share|improve this answer
1  
Great! I tested with Opera Mobile (which is SNI compliant but not in the list) and it doesn't redirect. With my Firefox, it does redirect! –  djechelon May 16 '12 at 21:10
4  
I suggest modularizing this solution with the BrowserMatch directive from mod_setenvif httpd.apache.org/docs/2.2/mod/mod_setenvif.html . Use BrowserMatch to set an environment variable supports_sni=y, then write RewriteCond %{ENV:supports_sni} =y. That way, you can reuse the SNI detection logic for any other RewriteRules that you might have. –  200_success May 16 '12 at 22:58
    
@200_success Good idea! –  Shane Madden May 17 '12 at 0:02

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.

share|improve this answer

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.

share|improve this answer

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.

share|improve this answer

I think it should be possible to modify the first example in http://wiki.apache.org/httpd/NameBasedSSLVHostsWithSNI:

<VirtualHost *:443>
  # Because this virtual host is defined first, it will
  # be used as the default if the hostname is not received
  # in the SSL handshake, e.g. if the browser doesn't support
  # SNI.
  DocumentRoot /www/example1
  ServerName www.example.com

  # Other directives here

</VirtualHost>

Specify a ServerName that you don't really use. Then put a redirect here.

share|improve this answer
    
This will throw a name/cert mismatch error; they will only be downgraded back to HTTP (assuming you've set up an appropriate redirect) after accepting the mismatched cert. Further, you can't auto-upgrade them to HTTPS without some sort of state maintenance, or they'll jump right back into the SSL VHost again. –  BMDan May 16 '12 at 20:57
    
@BMDan: Thinking about it, you're right. I'm leaving my answer here just to point out, why the "simple" approach won't work. –  Chris Lercher May 16 '12 at 21:08

Just for anyone needing it.

If you have multiple hosts and want all them to be SSL-enabled in VirtualHosting (and you bought a certificate for each) try the new mod_djechelon_ssl

$ cat /etc/apache2/mod_djechelon_ssl.conf 
RewriteEngine on
# 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 [OR]
#Safari iThing
RewriteCond %{HTTP_USER_AGENT} Mozilla.*iPhone.*Safari [OR]
RewriteCond %{HTTP_USER_AGENT} Mozilla.*iPod.*Safari [OR]
RewriteCond %{HTTP_USER_AGENT} Mozilla.*iPad.*Safari [OR]
RewriteRule ^/(.*)$ https://%{HTTP_HOST}/$1 [R=permanent,L]

Usage:

<VirtualHost ip:80>
ServerName www.yourhost.com

Include /path/to/mod_djechelon_ssl.conf

[plain old Apache directives]
</VirtualHost>

<VirtualHost ip:443>
ServerName www.yourhost.com

[SSL-related directives]

[Copy and paste directives from above host]
</VirtualHost>
share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.