79

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?

1
  • 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, 2012 at 21:33

9 Answers 9

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.
4
  • 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. Mar 20, 2013 at 15:07
  • 4
    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.
    – Chris
    Feb 25, 2014 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, 2014 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, 2014 at 19:57
121

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>
14
  • 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. Nov 7, 2014 at 11:36
  • 4
    @Mathemats Have you got URL Rewrite installed in IIS? Feb 14, 2016 at 1: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, 2016 at 1:50
  • 2
    hstspreload.org wants the user to add ` ;includeSubDomains; preload` after the max-age value. options. Full line will be: <action type="Rewrite" value="max-age=31536000 ;includeSubDomains; preload" /> to get a pass on hstspreload.org Feb 27, 2018 at 13:25
  • 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, 2018 at 22:46
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.

8
  • 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, 2014 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) Jan 16, 2014 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. Aug 17, 2014 at 1:11
  • 1
    @ChrisMoschini I should have been clearer that the first Web.config line should be for an SSL-only site. Mar 4, 2015 at 11:01
  • 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 Jun 28, 2019 at 15:27
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;
    }
}
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;
  }
}
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"

}
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.

3
  • 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, 2014 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 either.
    – Chris
    Oct 26, 2014 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. Mar 4, 2015 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
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

You must log in to answer this question.

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