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
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 new
Forwardedheader. It's possible therefore that the
X-Forwarded-Forheader may start dropping out of use and you'd be expected to work with
The header I'm interested in in this case is
which simply details the scheme of the original request. If the
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
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
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.
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.
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
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
httpHandlers, or other elements. I assume
these have been pre-defined at a higher level, but haven't
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.
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.
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
- 2017-11-19 - First published
- 2020-11-22 - Updated formatting
- Using the MantisBT REST API when hosted on IIS
- Redirecting to HTTPS when using IIS behind a load balancer
- Installing the URL Rewrite module into Internet Information Services