1

Environment information:

  • Windows Server 2012 R2
  • IIS 8
  • IIS URL Rewrite Module 2 (13/04/2015) 7.2.2
  • Umbraco 7.11.1

Configuration description

We have an Umbraco site which is bound to two primary domain names, let's call them sitea.com and siteb.com - the sites are the same source code & deployment but completely different content. In IIS, the sites are also bound to www.sitea.com and www.siteb.com

Additionally, we have the site(s) secured with LetsEncrypt for all 4 domain names.

The site should be on HTTPS, and we also have the umbraco key for useSSL set to true.

<add key="umbracoUseSSL" value="true" />

Rewrite config

We have a number of redirects in place, the entire rewrite config is pasted below, anonymised:

    <rewrite>
            <rules>
                <rule name="Allow LetsEncrypt" patternSyntax="Wildcard" stopProcessing="true">
                    <match url=".well-known/*" />
                    <action type="None" />
                </rule>
                <rule name="Remove WWW" patternSyntax="Wildcard" stopProcessing="false">
                    <match url="*" />
                    <conditions>
                        <add input="{CACHE_URL}" pattern="*://www.*" />
                    </conditions>
                    <action type="Redirect" url="{C:1}://{C:2}" redirectType="Permanent" />
                </rule>
                <rule name="HTTPS" patternSyntax="Wildcard" stopProcessing="false">
                    <match url="*" />
                    <conditions>
                        <add input="{HTTPS}" pattern="off" />
                    </conditions>
                    <action type="Redirect" url="https://{HTTP_HOST}{REQUEST_URI}" redirectType="Permanent" />
                </rule>
            </rules>
        </rewrite>

To summarize, there are 3 rules

  1. A bypass rule so when LetsEncrypt is validating ~/.well-known it is not caught by the redirects
  2. A redirect which if you visit www.sitea.com or www.siteb.com you get redirected to {protocol}://sitea.com depending on which protocol you entered the site on
  3. A further redirect which detects if HTTPS is on, and if it isn't, redirects you to https://{domain}/{path}

The problem

Occassionally, as noted by our monitoring and manual validation, if you visit http://sitea.com then you get redirected to https://sitea.com:80

Note the addition of port 80 on the redirect URL. This causes the site to not load since we're specifying https but hitting the http binding.

If we reset the application pool for the site, the issue resolves itself immediately. This issue can persist for several hours and then can correct itself, however this could be caused by the application pool either resetting on an absolute time or as requested by the application (we haven't investigated this)

I can't see anything wrong in the redirect config that could be causing this and it's an issue which only occurs periodically. We have other websites on the same server in IIS with very similar redirect and binding structures but they do not exhibit the same symptoms.

If it's relevant, in Umbraco, there's two content trees, and each is bound to sitea.com and siteb.com respectively. I only mention this in case the issue is in the application rather than the URL rewrite config.

Has anyone seen this bug before? Is there a simple solution?

Adjusting the rewrite rule to include port & FRT

Following a suggestion from comments on my question, I enabled FRT using https://docs.microsoft.com/en-us/iis/extensions/url-rewrite-module/using-failed-request-tracing-to-trace-rewrite-rules

I first had to install that as an IIS module then "repair" the URL rewriter module to get the trace options to be available. I configured this with responses 300-301.

I waited for the issue to reoccur and then when it did, I checked the logs.

I can see that there is a REDIRECT_FROM_CACHE_ACTION as part of the URL rewrite data with a CachedRedirectedUrl value of https://sitea.com:80/ on requests after the fault occurs. This explains why the issue starts and then doesn't resolve automatically until an application pool reset, the redirect output is cached and the reset clears the cache. Cached redirect

Checking earlier FRT logs (to find where the request was cached from) leads me to the following: Failing redirect

Here we can see the HTTPS rule evaluation starts, matches but the resulting redirect URL after replacing the tokens in <action type="Redirect" url="https://{HTTP_HOST}{REQUEST_URI}" redirectType="Permanent" /> is https://sitea.com:80 (entry #28, RedirectURL)

I've modified the rewrite rules so that the HTTPS rule reads

<rule name="HTTPS" patternSyntax="Wildcard" stopProcessing="false">
          <match url="*" />
          <conditions>
            <add input="{HTTPS}" pattern="off" />
          </conditions>
          <action type="Redirect" url="https://{HTTP_HOST}:443{REQUEST_URI}" redirectType="Permanent" />
        </rule>

This doesn't seem like it should be necessary since the port should be getting inferred from the protocol when not specified but perhaps it's a bug in URL rewrite? After a few days the original issue occurred, this time issuing a redirect for https://sitea.com:80:443/ (which is an invalid redirect with two ports).**

Edit 1: I've pulled the source for u7.11.1 and checked usage of umbracoUseSSL and this appears to only be used inside the backoffice, some notification and api routes. The front end page rendering doesn't check this variable and doesn't attempt https redirects. So this suggests the issue is upstream from the application.

Edit 2: Merged my proposed answer with the original question because it didn't solve the issue (it made it worse)

2

1 Answer 1

0

The server variable {HTTP_HOST} includes the port number. You could try {SERVER_NAME} instead, or write a regex condition that captures just the hostname of the {HTTP_HOST} variable. Here's an example of the latter. Make sure trackAllCaptures="true"

<rewrite>
        <rules>
            <rule name="Allow LetsEncrypt" patternSyntax="Wildcard" stopProcessing="true">
                <match url=".well-known/*" />
                <action type="None" />
            </rule>
            <rule name="Remove WWW" patternSyntax="Wildcard" stopProcessing="false">
                <match url="*" />
                <conditions>
                    <add input="{CACHE_URL}" pattern="*://www.*" />
                </conditions>
                <action type="Redirect" url="{C:1}://{C:2}" redirectType="Permanent" />
            </rule>
            <rule name="HTTPS" patternSyntax="Wildcard" stopProcessing="false">
                <match url="*" />
                <conditions trackAllCaptures="true">
                    <add input="{HTTPS}" pattern="off" />
                    <add input="{HTTP_HOST}" pattern="(.*):?\d*" />
                </conditions>
                <action type="Redirect" url="https://{C:1}{REQUEST_URI}" redirectType="Permanent" appendQueryString="false" />
            </rule>
        </rules>
    </rewrite>

Also added appendQueryString="false" because {REQUEST_URI} already includes the query string.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .