28

How do you use IIS's url rewrite module to force users to use ssl while you are behind an elastic beanstalk load balancer?

Ross Pace
  • 799
  • 1
  • 6
  • 9

4 Answers4

42

This is more difficult than it sounds for a few reasons. One, the load balancer is taking care of ssl so requests passed from the load balancer are never using ssl. If you use the traditional rewrite rule you will get an infinite loop of redirects. Another issue to contend with is that the AWS healthcheck will fail if it receives a redirect response.

  1. The first step in the solution is to create a healthcheck.html page and set it in the root directory. It doesn't matter what the content is.
  2. Set your load balancer to use the healthcheck.html file for health checks.
  3. Add the rewrite rule below in your web.config's <system.webServer><rewrite><rules> section:

    <rule name="Force Https" stopProcessing="true">
       <match url="healthcheck.html" negate="true" />
       <conditions>
           <add input="{HTTP_X_FORWARDED_PROTO}" pattern="https" negate="true" />
       </conditions>
       <action type="Redirect" url="https://{HTTP_HOST}{REQUEST_URI}" redirectType="Permanent" />
    </rule>
    

Notice that the rule match is on anything but our healthcheck file. This makes sure the load balancer's health check will succeed and not mistakenly drop our server from the load.

The load balancer passes the X-Forwarded-Proto value in the header which lets us know if the request was through https or not. Our rule triggers if that value is not https and returns a permanent redirect using https.

Ross Pace
  • 799
  • 1
  • 6
  • 9
  • When I try to use this, my redirected URL has duplicate query string parameters. For example, my source is `http://www.example.com/Action?e=1` it gets rewritten as `https://www.example.com/Action?e=1&e=1` – Matt Houser Nov 18 '13 at 02:08
  • 1
    Quite late response but I had the exact same problem as @mh1141. To make it work, just remove the '{REQUEST_URI}' part – serge Aug 26 '14 at 13:14
  • I've tried this solution in IIS 8.5 and it doesn't seem to be working. I've added another simple rule to see if the rules are being read and run and the simple rule is working. Also, should note the health check page is working fine with the load balancer. Does anyone have any ideas why this wouldn't work in IIS 8.5? – Lereveme Mar 08 '15 at 18:54
  • 1
    I was having a hard time getting this working. I finally figured out that I only had a listener for port 443 on he load balancer and no 80. I guess it seems obvious now but giving it a suggestion to check if you're having problems getting this to work. – Lereveme Mar 08 '15 at 19:01
  • 3
    The solution doesn't quite work for me. As Matt Houser suggests the query string parameters are duplicated. Unfortunately if you remove `{REQUEST_URI}` from the redirect url you lose the query string parameters entirely on redirect. To get it working I unticked "Append query string" in IIS. The action becomes `` – Phil Hale Dec 02 '15 at 14:10
  • Is it true about the health check URL? I've got mine working without it... I wonder if I'm missing something symptomatic that I'll discover later down the line. – Luke Jun 14 '16 at 17:37
  • If you leave the health check blank it continues to work, but if you customise the URL without excluding it from the rule it doesn't. Great answer. – Leonardo Chaia Apr 18 '17 at 16:44
14

Firstly I want to thank Ross for his original answer, it set me on my way to building up an IIS URL Rewrite rule that worked for me by using my existing HTTP to HTTPS redirect rule that I used before my website was behind an AWS Elastic Load Balancer.

<rule name="Redirect to HTTPS" enabled="true" stopProcessing="true">
    <match url="(.*)" />
    <conditions>
        <add input="{HTTP_X_FORWARDED_PROTO}" pattern="https" negate="true" />
        <add input="{REMOTE_HOST}" pattern="localhost" negate="true" />
        <add input="{REMOTE_ADDR}" pattern="127.0.0.1" negate="true" />
        <add input="{HTTP_HOST}" pattern="localhost" negate="true" />
    </conditions>
    <action type="Redirect" url="https://{HTTP_HOST}/{R:1}" redirectType="Permanent" />
</rule>

This rule allows you to access your site locally within Visual Studio or on the server on port 80 without having to access over HTTPS, so you only have to have a binding for port 80 on the server. It doesn't suffer from the things that others have mentioned (Duplicated querystring etc.).

I personally haven't had any issue with the health check, I didn't need to create a file on the server for the elastic load balancer to ping. I have my load balancer set to health check on TCP:80 and it works.

Luke
  • 22,826
  • 31
  • 110
  • 193
  • 1
    Have you got `RequireHttps` attributes on your controller or action, if you have you shouldn't have. This requires that all communication to back end instances is performed over HTTP. The ELB becomes the finalisation point for SSL/HTTPS. – Luke Jul 22 '16 at 15:20
  • 1
    Ah, okay, I see what it's doing now. Yes, that was the problem, and removing that filter fixed it. Thank you! – vaindil Jul 22 '16 at 15:29
  • I had to create this rule in IIS Manager, then go back and edit the config manually to work right. The conditions section was set to match any because match all didnt work. Also had to untick "Append query string" in IIS. – cde Dec 13 '16 at 19:01
  • I am trying to connect Google Analytics to my website using your code @Luke however it fails to connect since the only traffic that is allowed is HTTPS. What would you recommend to solve this issue? – HereToLearn Feb 03 '18 at 00:09
1

Luke's answer works perfect if you are using an ELB but will not work with an ALB. For an ALB Ross Pace answer is correct. But you can also combine the two so that way you can access the site locally without being redirected to HTTPS.

<rule name="Redirect to HTTPS" enabled="true" stopProcessing="true">
    <match url="healthcheck.html" negate="true" />
        <conditions>
            <add input="{HTTP_X_FORWARDED_PROTO}" pattern="https" negate="true"/>
            <add input="{REMOTE_HOST}" pattern="localhost" negate="true"/>
            <add input="{REMOTE_ADDR}" pattern="127.0.0.1" negate="true"/>
            <add input="{HTTP_HOST}" pattern="localhost" negate="true"/>
        </conditions>
    <action type="Redirect" url="https://{HTTP_HOST}/{R:1}" redirectType="Permanent"/>
</rule>
Trevor Orr
  • 11
  • 2
0

This worked for my application - IIS 8.5, redirect HTTP to HTTPS behind an AWS ALB. The key was adding appendQueryString="false" to prevent the query string duplication on redirect. You can add the traps for health check and localhost processing as needed. I did not need to add the health check trap, as we added this to the web.config of the app, making it app specific. Our health check is the default app on the domain, so it was not affected.

  <system.webServer>
  <rewrite>
  <rules>
          <rule name="Redirect to HTTPS" enabled="true" stopProcessing="true">
              <match url="^(.*)$" />
              <conditions>
                  <add input="{HTTP_X_FORWARDED_PROTO}" pattern="https" negate="true"/>
              </conditions>
              <action type="Redirect" url="https://{HTTP_HOST}{REQUEST_URI}" appendQueryString="false" redirectType="Permanent"/>
          </rule>
      </rules>
  </rewrite>

NJITman
  • 171
  • 1
  • 7