Generic IIS URL Rewrite Rules – Three common scenarios

Download & install rewrite module

The first hurdle in achieving this is that you don’t want to use the Redirect IIS module, but the Rewrite one which has to be downloaded separately. You can download it from here, but I script my server bootstrapping so preferred to use PowerShell for this. See below for an example on how to programmatically download and silently install the module.

Invoke-WebRequest -Uri https://download.microsoft.com/download/1/2/8/128E2E22-C1B9-44A4-BE2A-5859ED1D4592/rewrite_amd64_en-US.msi -OutFile c:\rewrite_amd64_en-US.msi
msiexec.exe /i c:\rewrite_amd64_en-US.msi /quiet /L*v c:\rewrite-install.log

The next step is to change your applications Web.config file. Look for a <system.webServer> in the file, if one does not exist create it as a child element under <configuration> (the top level). In the system.webServer element, add a <rewrite> node. If you are using different files for environments using XML Document Transform e.g. Web.Production.config, and you want to apply your rule to a specific region, ensure that you add xdt:Transform=”InsertIfMissing” to the rewrite node. Don’t worry if your aren’t following exactly, a full example is shown below.

Generic HTTPS redirect

Recently I was changing my legacy website to do SSL termination at the load balancer rather than directly on the host. This actually created a bit of a tricky situation, as my site allowed both HTTP and HTTPS users, and used the current connection to know which one the user was using (HttpRequest.IsSecureConnection). Wanting to move to a secure only environment, I wanted to create an IIS rewrite rule to redirect all users currently using HTTP to HTTPS automatically.

<?xml version="1.0"?>
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
  <system.webServer>
    <rewrite xdt:Transform="InsertIfMissing">
      <rules>
        <rule name="HTTPS force" 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}/{R:1}" redirectType="Permanent" />
        </rule>
      </rules>
    </rewrite>
  </system.webServer>
</configuration>

Now we have our rewrite element, we can start writing rules. You can use what is above and hopefully most of it should be pretty straight forward:

  • rule.name = give it a human readable name
  • rule.enabled = is the rule active?
  • rule.stopProcessing = if the rule is matched, should it stop doing anything further, for all our rules as we are redirecting the user this should be true
  • match.url = what URLs should it match on, here it matches on anything and we use the conditions to activate or not
  • add.input = what variable should we match on, in this case we are using the X-Forwarded-Proto header that load balancers will pass on. This is the key bit of magic to get the HTTPS redirect working behind a load balancer as all requests coming to your application are actually going to be HTTP. This header is a de-facto standard header for identifying the protocol (HTTP or HTTPS) that a client used to connect to your proxy or load balancer.
  • add.pattern = we want to check for https
  • add.negate = if we find “https” in the header, then we don’t want to activate this rule
  • action.type = we want to redirect users to our HTTPS URL
  • action.url = the url to redirect them, use the same host but with a https:// protocol. Also include anything after the hostname {R:1}
  • action.redirectType = return a 301 permanent redirect to let users know to never try it again

Enforce www subdomain

I thought while I was redirecting users URLs, I might as well enforce users are using the www subdomain as well. This may help with SEO because if you handle the same content with and without the subdomain it can sometimes think your content is duplicated and penalise you for it. It also keeps all the users having a consistent experience.

<rule name="WWW force" enabled="true" stopProcessing="true">
  <match url="(.*)" />
  <conditions>
    <add input="{CACHE_URL}" pattern="^(.+)://(?!www)(.*)" />
  </conditions>
  <action type="Redirect" url="{C:1}://www.{C:2}" redirectType="Permanent" appendQueryString="false" />
</rule>

This rule is fairly similar to the one above, this time it uses the CACHE_URL to test against our regex. This variable contains the entire URL string (e.g. http://www.localtest.me:80/info.aspx?id=5) so we can easily extract the protocol in the first capture group {C:1}, see if the host does NOT start with www, then capture the rest of the path {C:2}. If it matches, it will redirect the user to the same protocol, same path but now with the www added! You can use this rule in combination with the previous one or on it’s own. One gotcha is that the redirect action will add the query string onto the URL, but because we already captured it in the CACHE_URL, we don’t want to append them again so we can turn them off.

Old domain to new domain name

This one is not generic and will need you to enter the old domain name that you want to redirect users, and the new domain that you want to send them to. If you have a way of making this generic, please leave a comment, I would love to see how you solved it.

<rule name="New domain force" enabled="true" stopProcessing="true">
  <match url="(.*)" />
  <conditions>
    <add input="{HTTP_HOST}" pattern="^(www.)?old-domain.com$" />
  </conditions>
  <action type="Redirect" url="https://www.new-domain.com/{R:1}" redirectType="Permanent" />
</rule>

This one matches the HTTP_HOST variable, checking if it matches old-domain.com with an optional www subdomain. If it matches, it will redirect users to www.new-domain.com, preserving both the path and query string.

Tags :

About the Author

Mannan

Mannan is a software engineering enthusiast and has been madly coding since 2002. When he isn't coding he loves to travel, so you will find both of these topics on this blog.

Leave a Reply

Your email address will not be published. Required fields are marked *