IIS & Web.config redirects with rewrite rules - https, www, and more
Creating a new rule
To create a new rule, launch IIS Manager, and click a website:
See the URL Rewrite icon below the IIS section? If not, make sure to download and install the URL Rewrite extension: https://www.iis.net/downloads/microsoft/url-rewrite.
Double-click the URL Rewrite icon and click the Add Rule(s)... link from the Actions menu:
There are a wide range of different templates to choose from. As a first-time rule developer, getting a bit of assistance is a nice little feature of the IIS Manager. I'm not saying you shouldn't learn the underlying syntax (you need to), but looking at pre-generated examples is a good starting point.
Select the Append or remove the trailing slash symbol template and click the OK button. In the following step, keep the Appended if it does not exist selection and click OK. A new inbound rule is created:
Requesting the website without a trailing slash (/
) will automatically append the slash using the newly created rule.
Since the rule was created directly on the website, the new rule is added to the web.config
file of the website. You hopefully understand why this approach shouldn't be used in your production environment. Every piece of configuration should be in either source control or deployment software like Octopus Deploy or Azure DevOps. For playing around with rules, the UI approach is excellent, though.
Let's take a look at the generated code inside the web.config
file:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.webServer>
<rewrite>
<rules>
<rule name="AddTrailingSlashRule1" stopProcessing="true">
<match url="(.*[^/])$" />
<conditions>
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
</conditions>
<action type="Redirect" url="{R:1}/" />
</rule>
</rules>
</rewrite>
</system.webServer>
</configuration>
At first glance, rewrite rules can be a challenge to read. If you feel this way, I understand you. Configuring software in deeply nested XML never been my favorite either. Let's go through the markup one element at the time.
Inbound and outbound rules
All rules must be added in the <rewrite>
element inside <system.webServer>
. There are two types of rules available: inbound rules (defined in the <rules>
element) and outbound rules (defined in the <outboundRules>
element). In the example above, the rewrite rule is added as an inbound rule. Inbound rules are executed against the incoming request. Examples of these rules are: add a trailing slash (yes, the example from previous), rewrite the request to a different URL, and blocking incoming requests based on headers. Outbound rules are executed against the response of an HTTP request. Examples of outbound rules are: add a response header and modify the response body.
Both inbound and outbound rules are defined in a <rule>
element. In the example above, a new rule named AddTrailingSlashRule1
is added. The name of each rule is left for you to decide, as long as you give each rule a unique name. Also, take notice of the stopProcessing
attribute specified on the rule. If set to true, IIS will stop executing additional rules if the condition specified inside the rule matches the current request. You mostly want to specify false
in this attribute, unless you are redirecting the entire request to another URL.
Match, action, and conditions
All rules have a pattern, an action, and an optional set of conditions. The example above has all three.
The pattern is implemented in the <match> element and tells the rewrite extension which URLs to execute the rule for. Patterns are written using regular expressions in the url attribute. Regular expressions is an extremely complicated language, which deserves a very long blog post on its own. I write regular expressions by using Google to find examples of how people already wrote examples of regular expressions looking like what I would like to achieve in each case.
The <action>
element tells IIS what to do about requests matching the pattern. All actions need a type
attribute to tell IIS how to move forward. type
can have one of the following values:
None
: do nothing.Rewrite
: rewrite the request to another URL.Redirect
: redirect the request to another URL.CustomResponse
: return a custom response to the client.AbortRequest
: drop the HTTP connection for the request.
In the example above, the action used a Redirect
type, which means that the web server will return an HTTP redirect to the client. You can specify if you want to use a permanent or temporary redirect using the redirectType
attribute.
I don't want to list every possible element and value inside this post since the official MSDN documentation is quite good. Visual Studio also provides decent IntelliSense for rewrite rules. Browse through the examples last in this post to get an overview of what is possible with rewrite rules.
Common rules
This is a collection of common rules that we are either using on elmah.io or I have used in the past.
Redirect to HTTPS
<rule name="RedirectToHTTPS" stopProcessing="true">
<match url="(.*)" />
<conditions>
<add input="{HTTPS}" pattern="off" ignoreCase="true" />
</conditions>
<action type="Redirect" url="https://{SERVER_NAME}/{R:1}" redirectType="Permanent" />
</rule>
Redirect www to non-www
<rule name="RedirectWwwToNonWww" stopProcessing="false">
<match url="(.*)" />
<conditions logicalGrouping="MatchAll" trackAllCaptures="false">
<add input="{HTTP_HOST}" pattern="^(www\.)(.*)$" />
</conditions>
<action type="Redirect" url="https://{C:2}{REQUEST_URI}" redirectType="Permanent" />
</rule>
Redirect non-www to www
<rule name="RedirectNonWwwToWww" stopProcessing="true">
<match url="(.*)" />
<conditions>
<add input="{HTTP_HOST}" pattern="^domain.com$" />
</conditions>
<action type="Redirect" url="http://www.domain.com/{R:0}" redirectType="Permanent" />
</rule>
Reverse proxy
In this example, I'm using rewrite rules to serve a blog hosted on GitHub but through a custom domain name. By using the Rewrite
action type, the URL stays the same, even though the content is served from elmahio.github.io
.
<rule name="ReverseProxy" stopProcessing="true">
<match url="(.*)" />
<conditions logicalGrouping="MatchAll">
<add input="{REQUEST_URI}" negate="true" pattern="^(.*)/.well-known/(.*)$"/>
</conditions>
<action type="Rewrite" url="http://elmahio.github.io/blog/{R:1}" />
</rule>
Rewrite all but elmah.axd
<rule name="RewriteAllButElmahAxd" stopProcessing="true">
<match url="(.*)" />
<conditions>
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
<add input="{URL}" negate="true" pattern="\elmah.axd$" />
</conditions>
<action type="Rewrite" url="http://elmahio.github.io/blog/{R:1}" />
</rule>
Add trailing slash
<rule name="AddTrailingSlash" stopProcessing="true">
<match url="(.*[^/])$" />
<conditions>
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
</conditions>
<action type="Redirect" url="{R:1}/" redirectType="Permanent" />
</rule>
Lowercase all URLs
<rule name="LowercaseAllUrls" stopProcessing="true">
<match url=".*[A-Z].*" ignoreCase="false" />
<action type="Redirect" url="{ToLower:{R:0}}" />
</rule>