Redirecting to HTTPS when using IIS behind a load balancer
Cyotek has a number of different websites powering various bits of our software and services. Some are Windows based using IIS, and some are Linux based using Apache. Internal servers are are directly accessed but external servers are behind load balancers. Almost all are using HTTPS now and have redirects in place to force the use of HTTPS over HTTP.
There are plenty of articles on the internet dealing how to use
.htaccess
files to perform redirects using Apache, and various
articles on different methods of redirecting IIS or ASP.NET
applications. However, there seems to be a slight gap when it
comes to load balancers or reverse proxies. Depending on how the
load balancer / reverse proxy (referred to as just load balancer
for the rest of the article) operates, the secure connection may
terminate at the load balancer, and so the web server always
receives "insecure" traffic.
If the secure connection is terminated at the balancer, then this can be a problem for the most common method of redirecting traffic, which is to check if the current request's scheme is HTTP and perform a redirect if it is. But if the web server always gets HTTP traffic, then this approach will simply result in an infinite redirect loop, something I've managed to do inadvertently more than once.
Fortunately however, when a load balancer forwards traffic to
the final server, it generally includes some extra headers such
as X-Forwarded-For
and X-Forwarded-Proto
. These headers
provide details such as the original scheme/protocol being used
and the origin IP address.
When researching the final details for writing this article, I found that the non-standard
X-Forwarded-*
headers have been replaced under RFC 7239 by a newForwarded
header. It's possible therefore that theX-Forwarded-For
header may start dropping out of use and you'd be expected to work withForwarded
instead.
The header I'm interested in in this case is X-Forwarded-Proto
which simply details the scheme of the original request. If the
value is http
, then clearly the original request wasn't secure
and so I can issue a redirect. If it's https
, then the
original request - up to hitting the load balancer - was secure,
and we shouldn't try and redirect, or we're back into infinite
loop territory.
Remember that these are request headers, e.g. headers you can't control and which could be forged or manipulated.
When using IIS, I've mostly used code based redirects although
the very oldest bits use an ancient configuration based
re-writer named Intelligencia.UrlRewriter
. However, for some
time IIS now has had an optional URL rewriter of its own, which
is what I'm going to use in this article.
By default the URL rewrite module isn't installed, although it's easy enough to add using the Web Platform Installer. I've detailed the steps in a prior post.
Adding a rule to force HTTPS
Although you can configure rules from within IIS Managers GUI
interface, I'm just going to detail how to directly modify the
web.config
of an application.
In your web.config
, find the system.webServer
element (or
add it as a child of configuration
if it is not already
present), and add a rewrite
element containing the following
content.
<rewrite>
<rules>
<!-- Specify a rule element. The stopProcessing attribute means no other
rules will be processed if this rule is a match. You can also add a boolean
attribute named enabled which you can use to temporarily disable a rule
without deleting it from the file -->
<rule name="HTTPS Redirect" stopProcessing="true">
<!-- Specifies a regular expression used to match the incoming URL. As I
want all URL's to be considered, I tell it to match any character. By wrapping
the expression in brackets I create a capturing group I can use elsewhere. -->
<match url="(.*)" />
<!-- Each rule can have one or more conditions -->
<conditions>
<!-- Here I've added a condition which looks at the X-Forwarded-Proto
header to see if it's any value other than "https". (The attribute
negate="true" means "does not match") -->
<add input="{HTTP_X_FORWARDED_PROTO}" pattern="https" negate="true" />
</conditions>
<!-- The action element states what should happen if all the rule
conditions are met. This action instructs IIS to perform a 301 redirect
to the original host (via the HTTP_HOST variable) with the original request
path (as captured in the match element above) via R:1 -->
<action type="Redirect" url="https://{HTTP_HOST}/{R:1}" />
</rule>
</rules>
</rewrite>
Rather than trying to add a description outside the code block, I've added a bunch of comments inside the XML.
Happily, these are the only changes you need to make to
web.config files - you don't need to define new
configSections
, httpHandlers
, or other elements. I assume
these have been pre-defined at a higher level, but haven't
confirmed this.
A note on using LetsEncrypt renewal challenges
If you are renewing LetsEncrypt certificates via a HTTP challenge (where a file is placed on your server to confirm validity), then this rule will cause the challenge to fail due to the redirect. If your processes are set to only renew the certificates at the last minute then SSL can stop working on your website, causing web browsers to display unwelcome danger messages when visitors attempt to access your site. Somethign else that has happened more than once to me, and the main reason why cyotek.com is now running off a 3 year traditional certificate after lasts months mishap.
As the file is always in a static location
(.well-known/acme-challenge
) we can expand our original rule
to add a new condition which prevents the redirect from
triggering if that path is present.
Using the example rule above, I added a new child of the
conditions
element as follows.
<add input="{PATH_INFO}" pattern="^/\.well-known/acme-challenge/.*" negate="true" />
When testing a HTML located in this path, the request was not forcibly redirected to HTTPS and was displayed via unsecure HTTP, which should allow LetsEncrypt challenges to succeed. Of course, the certificates on the domains where I've implemented these rules aren't due to expire for another two months so time will tell if this is sufficient.
Closing notes
Forcing a redirect from HTTP to HTTPS is only one part - you should also consider using a Content Security Policy (CSP) that instructs browsers to automatically use HTTPS and setting up HTTP Strict Transport Security (HSTS). You should also ensure the redirects you put in place are 301 to guide search engines to update references (the example above uses a 301 redirect). Lots of things to try and remember! Troy Hunt has a helpful post on getting started with HTTPS.
There is also lots more you can do using rewrite rules - you can
view the documentation for more information and examples.
And, although in this article I only covered modifying
web.config
directly, you can use the GUI tools in IIS Manager
to explore all the supported function and experiment with rule
creation.
Update History
- 2017-11-19 - First published
- 2020-11-22 - Updated formatting
Related articles you may be interested in
Leave a Comment
While we appreciate comments from our users, please follow our posting guidelines. Have you tried the Cyotek Forums for support from Cyotek and the community?
Comments
Stefan Richter
#
Thank you for this, I've finally managed to solve a complicated setup behind an EC2 load balancer that uses an ancient version of IIS. I've never managed to redirect http to https on that machine without causing a redirect loop - the HTTP_X_FORWARDED_PROTO condition was the crucial piece to the puzzle. 👍
Richard Moss
#
Stefan,
You're welcome, I'm glad this article was helpful for you!
Regards;
Richard Moss
Damian
#
Hi Richard Any idea on how to fix "Mixed Content" issues with a very similar setup. I am only experiencing it on WEBP images though, very strange
Richard Moss
#
Hello,
The use of a load balancer shouldn't affect this and it is more likely caused by how your HTML is generated. It sounds like you are serving pages with absolute URLs that point to http. Assuming these aren't from an external domain that you can't control, I have seen this when using MVC URL helpers to generate an absolute URL. Given your description of only experiencing these for WEBP images suggests you have a custom handler to support this format, and perhaps this isn't constructing URLs correctly. If this is the case, consider trying to use a relative URL, or by using
//
, e.g//example.com/image.webp
and let the browser choose the appropriate protocol.Regards;
Richard Moss