78

What is the best way to turn on HTTP Strict Transport Security on an IIS 7 web server?

Can I just through the GUI and add the proper HTTP response header or should I be using appcmd and if so what switches?

Bob
  • 2,559
  • 3
  • 25
  • 22
  • 1
    A lot of this depends on *how* you're generating the stuff IIS is serving (for example. you can set the header in PHP or ASP.NET pages from within your application). Can you tell us more about your use case? – voretaq7 Aug 13 '12 at 21:33

9 Answers9

119

This allows us to handle both the HTTP redirect and add the Strict-Transport-Security header to HTTPS responses with a single IIS site (URL Rewrite module has to be installed):

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <system.webServer>
        <rewrite>
            <rules>
                <rule name="HTTP to HTTPS redirect" stopProcessing="true">
                    <match url=".*" />
                    <conditions>
                        <add input="{HTTPS}" pattern="off" ignoreCase="true" />
                    </conditions>
                    <action type="Redirect" url="https://{HTTP_HOST}{REQUEST_URI}"
                        redirectType="Permanent" />
                </rule>
            </rules>
            <outboundRules>
                <rule name="Add Strict-Transport-Security when HTTPS" enabled="true">
                    <match serverVariable="RESPONSE_Strict_Transport_Security"
                        pattern=".*" />
                    <conditions>
                        <add input="{HTTPS}" pattern="on" ignoreCase="true" />
                    </conditions>
                    <action type="Rewrite" value="max-age=31536000; includeSubDomains; preload" />
                </rule>
            </outboundRules>
        </rewrite>
    </system.webServer>
</configuration>
Santhos
  • 103
  • 3
Doug Wilson
  • 1,293
  • 2
  • 9
  • 7
  • 7
    Thanks, this is best answer! Adds the header to static HTML files too, unlike the programatic approach. And doesn't add to HTTP, thus conforming to the standard. – Jeow Li Huan Nov 07 '14 at 11:36
  • @Doug The headers are missing for the files that load from my server, when I load the page for the first time. Moreover when I refresh the page, they can be seen. Any specific reason for that? – Parth Nov 23 '15 at 12:12
  • @Dr..Net Sounds like you should post a new question so that we can see your code and work from there. – Doug Wilson Nov 23 '15 at 23:51
  • @DougWilson here(I've posted with my actual proper findings so the question might differ a little) http://stackoverflow.com/questions/33873283/ssl-error-on-implementing-hsts/33881350#33881350 – Parth Nov 24 '15 at 04:56
  • 1
    When I add this to my section of web.config I get a 500 error on my asp.net pages. There doesn't seem to be a child for this node (https://msdn.microsoft.com/en-us/library/ms689429.aspx). – Mathemats Feb 11 '16 at 04:20
  • 4
    @Mathemats Have you got URL Rewrite installed in IIS? – Doug Wilson Feb 14 '16 at 01:33
  • 3
    No, after more research I found out that the rewrite tag is provided by the extension (d'oh). All of the answers that I could find don't mention the extension as a dependency, perhaps you could throw a one liner in your answer saying you need it. – Mathemats Feb 14 '16 at 01:50
  • 2
    https://hstspreload.org/ wants the user to add ` ;includeSubDomains; preload` after the max-age value. options. Full line will be: `` to get a **pass** on hstspreload.org – JP Hellemons Feb 27 '18 at 13:25
  • I copied this exact web.config and am getting a "too many redirects" error. (using IIS 8.5) If I disable the first rule within then the error stops but then it doesn't redirect as desired. – mindmischief May 17 '18 at 15:51
  • 2
    The capture group R:1 with the pattern (.*) matches the entire URL, protocol and all, and trying to concatenate {HTTP_HOST}/{R:1} means you get `https://somedomain.com/https://somedomain.com/relatedpath` and the result is the path is dropped off. – AaronLS Sep 11 '18 at 22:46
  • 1
    Instead use the action `url="https://{HTTP_HOST}{REQUEST_URI}"` to preserve the path across the redirect. – AaronLS Sep 11 '18 at 22:50
  • 1
    As @AaronLS points out, the rewrite rule is incorrect. See https://docs.microsoft.com/en-us/iis/extensions/url-rewrite-module/url-rewrite-module-configuration-reference – Santhos Feb 26 '19 at 07:45
  • The answer has been fixed – Santhos Feb 26 '19 at 22:09
  • Does this apply for IIS 8.5 as well? I am getting `... has a security policy called HTTP Strict Transport Security (HSTS), which means that Firefox can only connect to it securely. You can’t add an exception to visit this site.` on client machines. I can access just fine on the server itself. – Code Maverick Apr 25 '19 at 22:17
40

To supplement voretaq7's answer, you could also do this using the Web.config file (N.B.: To be used for SSL sites only, since it will add the header for both HTTP and HTTPS responses, which is against the RFC 6797 specification, please see the explanation below) — add a block as follows:

<system.webServer>
    <httpProtocol>
        <customHeaders>
            <add name="Strict-Transport-Security" value="max-age=31536000"/>
        </customHeaders>
    </httpProtocol>
</system.webServer>

Obviously, you may already have a system.webServer block in your Web.config, so add this to that, if so. We prefer handling things in the Web.config rather than the GUI, because it means the config changes can be committed to our Git repository.

If you wanted to handle the HTTP-to-SSL redirection, as Greg Askew mentioned, you might find it easier to do that with a separate website in IIS. This is how we handle requiring SSL for some client sites. That site contains only an HTTP redirect and some information-disclosure fixes, all in the Web.config:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <system.web>
    <httpRuntime requestValidationMode="2.0" enableVersionHeader="false" />
  </system.web>
  <system.webServer>
    <httpRedirect enabled="true" destination="https://www.domain.co.uk/"
      httpResponseStatus="Permanent" />
    <httpProtocol>
      <customHeaders>
        <remove name="X-Powered-By" />
      </customHeaders>
    </httpProtocol>
    <rewrite>
      <outboundRules>
        <rule name="Remove RESPONSE_Server">
          <match serverVariable="RESPONSE_Server" pattern=".+" />
          <action type="Rewrite" value="" />
        </rule>
      </outboundRules>
    </rewrite>
  </system.webServer>
</configuration>

This is our preferred solution for a couple of reasons — we can easily log redirected traffic separately (as it's in a different IIS log), it doesn't involve more code in the Global.asax.cs (we don't have any code in there, which is a little more convenient for an Umbraco site) and, importantly, it means that all the config is still held in our GIT repo.

Edited to add: To be clear, in order to comply with RFC 6797, the Strict-Transport-Security custom header MUST NOT be added to requests made by unencrypted HTTP. To be RFC6797-compliant, you MUST have two sites in IIS, as I've described after the first code block. As Chris points out, RFC 6797 includes:

An HSTS Host MUST NOT include the STS header field in HTTP responses conveyed over non-secure transport.

so sending the Strict-Transport-Security customer header in response to a non-SSL request would not comply with the specification.

Owen Blacker
  • 631
  • 1
  • 7
  • 20
  • 1
    To add to Owen Blacker response, for IIS I use URLScan 3.1 and have it globally remove the SERVER from the response by setting RemoveServerHeader=1, the rest of the settings have mandated to be in each site web.config file. I prefer this to just blanking out the value. – KeyOfJ Jan 14 '14 at 20:50
  • URLScan is a very common solution and, I would suggest, a better one than the one I'm suggesting. But it's not always the most convenient solution :o) – Owen Blacker Jan 16 '14 at 13:35
  • Important to note that adding this to a site with HTTPS and HTTP enabled (so it can redirect) will BREAK the site! You'll get an informationless 500, even with CustomErrors Mode="Off", with no errors in the logs. – Chris Moschini Aug 17 '14 at 01:11
  • 1
    @ChrisMoschini I should have been clearer that the first Web.config line should be for an SSL-only site. – Owen Blacker Mar 04 '15 at 11:01
  • Why isn't the Strict-Transport-Security allowed in non-SSL? It would be an indicator for the browser to change to https. A bug in the specification, or is there a specific reason? – Lenne Mar 03 '17 at 09:51
  • 1
    @Lenne Scott Hanselman wrote a more very good description of why STS doesn't belong in the header while using HTTP. Read more [here](https://www.hanselman.com/blog/HowToEnableHTTPStrictTransportSecurityHSTSInIIS7.aspx) – David Yates Jun 28 '19 at 15:27
  • @OwenBlacker, if you should have been clearer just edit your post with the details instead of leaving a comment. This question gets a lot of traffic so it would be helpful. – user2320464 Apr 06 '20 at 18:18
  • @user2320464 You're right, though that edit has already been made (on 9 Dec 2016) by user Jyrkka – Owen Blacker Apr 07 '20 at 15:09
17

IIS has the ability to add custom headers to responses. This would seem to be the easiest way to go about it.

According to the documentation on IIS.net you can add these headers through IIS Manager:

  • In the Connections pane, go to the site, application, or directory for which you want to set a custom HTTP header.
  • In the Home pane, double-click HTTP Response Headers.
  • In the HTTP Response Headers pane, click Add... in the Actions pane.
  • In the Add Custom HTTP Response Header dialog box, set the name and value for your custom header, and then click OK.
voretaq7
  • 79,345
  • 17
  • 128
  • 213
  • 5
    It's also possible to do this in the Web.config, which you might prefer. I've posted the details as a new answer, as they'd be really difficult to read without the sourcecode formatting that's not available in comments. – Owen Blacker Mar 20 '13 at 15:07
  • 4
    According to the makers of [HTTP Strict Transport Security IIS Module](http://hstsiis.codeplex.com/), just adding the custom header is not compliant with the draft specification (RFC 6797). You would actually need to install this IIS Module. – Chris Feb 25 '14 at 19:16
  • @Chris They're (kinda) wrong. Not about the spec - they're absolutely correct there - but about the fact that there's no "simple" way to comply aside from their module: Just create 2 sites, one for SSL (with the header) and one for non-SSL (without the header). Certainly the module is a little more *elegant*, but it's not *necessary* (and not warranted at all if your site is https-only and you don't serve plain HTTP responses). – voretaq7 Feb 25 '14 at 19:55
  • 1
    @Chris You should add an answer referencing that module though - free upvotes! (I wasn't aware of it's existence, and for a lot of folks it's probably an easier/better option than the custom header stuff) – voretaq7 Feb 25 '14 at 19:57
9

I would use the example from the Wikipedia link you referenced and perform the activity in global.asax for the site. This enables redirecting the request to an https url, and then insert the header into the response.

This is due to the HSTS header must be ignored if it isn't in an https response.

protected void Application_BeginRequest()
{
    switch (Request.Url.Scheme)
    {
        case "https":
            Response.AddHeader("Strict-Transport-Security", "max-age=31536000");
            break;
        case "http":
            var path = "https://" + Request.Url.Host + Request.Url.PathAndQuery;
            Response.Status = "301 Moved Permanently";
            Response.AddHeader("Location", path);
            break;
    }
}
Owen Blacker
  • 631
  • 1
  • 7
  • 20
Greg Askew
  • 34,339
  • 3
  • 52
  • 81
3

This seems to be a pretty fail safe way of doing this. Add this code in the Global.asax - the Application_BeginRequest event fires first in the Asp.net request lifecycle: http://msdn.microsoft.com/en-us/library/system.web.httpapplication.beginrequest(v=vs.110).aspx

Per the spec, http requests must not respond with the header - so this code only adds it for https requests. Max-age is in number of seconds, and it's usually a good idea to put a large value in here (IE - 31536000 indicates the site will run SSL only for the next 365 days)

protected void Application_BeginRequest(Object sender, EventArgs e)
{
  switch (Request.Url.Scheme)
  {
    case "https":
      Response.AddHeader("Strict-Transport-Security", "max-age=31536000");
      break;
    case "http":
      var path = "https://" + Request.Url.Host + Request.Url.PathAndQuery;
      Response.Status = "301 Moved Permanently";
      Response.AddHeader("Location", path);
      break;
  }
}
Owen Blacker
  • 631
  • 1
  • 7
  • 20
erbz
  • 53
  • 3
2

Using the example provided by Doug Wilson I have created the following two PowerShell functions to add url rewrite rules for redirection to HTTPS and for adding HSTS headers.

These have been tested on Windows 2012 and Windows 2012 R2.

All you need to do is supply the website name. You can optionally give the rules a different name if you do not like the defaults.

One things to note is that from my testing, the Server Variables need to be added to the allow list before being in the response headers. The functions do this for you.

EDIT: See reference on Url Rewrite for HTTP Headers here: http://www.iis.net/learn/extensions/url-rewrite-module/setting-http-request-headers-and-iis-server-variables

Function Add-HTTPSRedirectRewriteRule()
{
    <#
        .SYNOPSIS
        This function is used to create a URL Rewrite Rule that redirects HTTP requests to HTTPS using a 301
        RuleName is optional and will default to "Redirect to HTTPS"

        .SYNTAX
        Add-HTTPSRedirectRewriteRule -WebsiteName "www.mywebsite.com"

        .EXAMPLES
        Add-HTTPSRedirectRewriteRule -WebsiteName "www.mywebsite.com"

        Add-HTTPSRedirectRewriteRule -WebsiteName "www.mywebsite.com" -RuleName "my rule name"

    #>


    [cmdletbinding(positionalbinding=$false)]
    Param
    (
        [parameter(mandatory=$true)][String] [ValidateNotNullOrEmpty()] $WebsiteName,
        [parameter(mandatory=$false)][String] $RuleName="Redirect to HTTPS"
    )

        Write-Verbose -Message "Creating the Url Rewrite rule ""$RuleName"" in website ""$WebsiteName"""
        Remove-WebConfigurationProperty -pspath "MACHINE/WEBROOT/APPHOST" -location "$WebsiteName" -filter "system.webServer/rewrite/rules" -name "." -AtElement @{name="$RuleName"}  -ErrorAction SilentlyContinue
        Add-WebConfigurationProperty -pspath 'MACHINE/WEBROOT/APPHOST' -location "$WebsiteName" -filter "system.webServer/rewrite/rules" -name "." -value @{name="$RuleName";stopProcessing='True'}
        Set-WebConfigurationProperty -pspath 'MACHINE/WEBROOT/APPHOST' -location "$WebsiteName" -filter "system.webServer/rewrite/rules/rule[@name='$RuleName']/match" -name "url" -value "(.*)"
        Add-WebConfigurationProperty -pspath 'MACHINE/WEBROOT/APPHOST' -location "$WebsiteName" -filter "system.webServer/rewrite/rules/rule[@name='$RuleName']/conditions" -name "." -value @{input='{HTTPS}';pattern='off'}
        Set-WebConfigurationProperty -pspath 'MACHINE/WEBROOT/APPHOST' -location "$WebsiteName" -filter "system.webServer/rewrite/rules/rule[@name='$RuleName']/action" -name "type" -value "Redirect"
        Set-WebConfigurationProperty -pspath 'MACHINE/WEBROOT/APPHOST' -location "$WebsiteName" -filter "system.webServer/rewrite/rules/rule[@name='$RuleName']/action" -name "url" -value "https://{HTTP_HOST}/{R:1}"
}

Function Add-HSTSHeaderRewriteRule()
{
    <#
        .SYNOPSIS
        This function is used to create a URL Rewrite Rule that sets an HTTP Response Header for Strict-Transport-Security
        when the protocol requested is HTTPS

        RuleName is optional and will default to "Add Strict-Transport-Security header when request is HTTPS"

        .SYNTAX
        Add-HSTSHeaderRewriteRule -WebsiteName "www.mywebsite.com"

        .EXAMPLES
        Add-HSTSHeaderRewriteRule -WebsiteName "www.mywebsite.com"

        Add-HSTSHeaderRewriteRule -WebsiteName "www.mywebsite.com" -RuleName "my rule name"

    #>

    [cmdletbinding(positionalbinding=$false)]
    Param
    (
        [parameter(mandatory=$true)][String] [ValidateNotNullOrEmpty()] $WebsiteName,
        [parameter(mandatory=$false)][String]$RuleName="Add Strict-Transport-Security header when request is HTTPS"
    )

    $serverVariable = "RESPONSE_Strict_Transport_Security"

    Write-Verbose -Message "Creating the HSTS Header rule ""$RuleName"" in website ""$WebsiteName"""

    Remove-WebConfigurationProperty -pspath 'MACHINE/WEBROOT/APPHOST' -location "$WebsiteName" -filter "system.webServer/rewrite/allowedServerVariables" -name "." -AtElement @{name="$serverVariable"} -ErrorAction SilentlyContinue
    Add-WebConfigurationProperty -pspath 'MACHINE/WEBROOT/APPHOST' -location "$WebsiteName"  -filter "system.webServer/rewrite/allowedServerVariables" -name "." -value @{name="$serverVariable"}

    Remove-WebConfigurationProperty -pspath "MACHINE/WEBROOT/APPHOST" -location "$WebsiteName" -name "." -filter "system.webServer/rewrite/outboundRules" -AtElement @{name="$RuleName"} -ErrorAction SilentlyContinue

    Add-WebConfigurationProperty -pspath "MACHINE/WEBROOT/APPHOST" -location "$WebsiteName" -filter "system.webServer/rewrite/outboundRules" -name "." -value @{name="$RuleName"}
    Set-WebConfigurationProperty -pspath "MACHINE/WEBROOT/APPHOST" -location "$WebsiteName" -filter "system.webServer/rewrite/outboundRules/rule[@name='$RuleName']/match" -name "serverVariable" -value $serverVariable
    Set-WebConfigurationProperty -pspath "MACHINE/WEBROOT/APPHOST" -location "$WebsiteName" -filter "system.webServer/rewrite/outboundRules/rule[@name='$RuleName']/match" -name "pattern" -value ".*"
    Add-WebConfigurationProperty -pspath "MACHINE/WEBROOT/APPHOST" -location "$WebsiteName" -filter "system.webServer/rewrite/outboundRules/rule[@name='$RuleName']/conditions" -name "." -value @{input='{HTTPS}';pattern='on'}
    Set-WebConfigurationProperty -pspath "MACHINE/WEBROOT/APPHOST" -location "$WebsiteName" -filter "system.webServer/rewrite/outboundRules/rule[@name='$RuleName']/action" -name "type" -value "Rewrite"
    Set-WebConfigurationProperty -pspath "MACHINE/WEBROOT/APPHOST" -location "$WebsiteName" -filter "system.webServer/rewrite/outboundRules/rule[@name='$RuleName']/action" -name "value" -value "max-age=31536000"

}
CarlR
  • 574
  • 5
  • 8
1

According to the makers of HTTP Strict Transport Security IIS Module, just adding the custom header is not compliant with the draft specification (RFC 6797).

You would actually need to install this IIS Module to turn on HSTS on IIS 7.

Update 26 okt 2014: Thanks to the commenter below, I read the module page again and specifically the part that justifies the use of the module over adding custom headers.

An HSTS Host MUST NOT include the STS header field in HTTP responses conveyed over non-secure transport.

If you make sure to add the headers only in HTTPS and NOT in HTTP, you don't need this module and you can use the answer by Doug Wilson. Don't use Owen Blacker's answer cause it doesn't have the https condition.

Chris
  • 488
  • 6
  • 14
  • 1
    So do some of the other answers that only send the header to HTTPS requests solve this problem as well? Or does your module do something different/extra that the other solutions do not? – slolife Oct 24 '14 at 18:04
  • @slolife I updated my answer. You can use the code in Doug Wilson's answer. You don't need this module. I see now that this is also discussed in the comments of the accepted answer. I'm not aware of this module doing anything different/extra that the other solutions do not. But I haven't done an exhaustive check of the [source code](https://github.com/AllTheDucks/hsts-iis-module/blob/master/module/src/module/cpp/HstsIisModule.cpp) either. – Chris Oct 26 '14 at 20:41
  • I should have been clearer that the first Web.config should be implemented in an SSL-only site. I'll edit my answer to clarify that. – Owen Blacker Mar 04 '15 at 11:01
1

This can be done by adding following block in Web.Config:

<system.webServer>
    <httpProtocol>
      <customHeaders>
        <add name ="CustomName" value="MyCustomValue"/>
      </customHeaders>
    </httpProtocol>
</system.webServer>

We have to configure on IIS that has the ability to custom headers to response:

  • Go to Internet Information Services(IIS) Manager.
  • Configure Response headers that are added to response from the server.
  • Now add your custom header Name and custom Value (Custom header name and value should be same as that in Web.Config). You can find on blog
Code Maverick
  • 113
  • 1
  • 8
Vinit
  • 11
  • 2
0

Just to add, I see in the comments 2 people talking about 500 errors when you do this. I had this.

If you get a 500 error in IIS it may be because you have added the rule both at the top level, set to inherited, and at the site level.

e.g.

Default Web Site <- here
  Some Web Site <- here

IIS/The Browser doesn't seem to give you any information that you have done this, regardless of your error handling settings

tony
  • 101