This site uses cookies for analytics. By continuing to browse this site, you agree to this use.
A JS Foundation Project

Basic `web.config` for IIS

Basic web.config for IIS

The following web.config should be a good starting point to pass most of webhint's checks that require adding to or modifying the server configuration.

There are some assumptions though:

  • The site is static. If you are using node with iisnode, ASP.NET, etc. you will have to add the required configuration (but most of this configuration should still be valid).
  • All the static assets are in the folder dist/static.
  • The static resources (CSS, JavaScript, images, etc.) have precompressed gzip and brotli versions. You can look into IIS.Compression if you want IIS to take care of that directly.
  • Any URL that ends with / is going to serve an static HTML page that is already in the file system.
  • The encoding of text based resources is utf-8.

Each section has a comment with a small explanation and the related hint:

 <!--
Explanation
"hint name": URL
-->

If you want to know more, it is recommended to visit the documentation of each related hint.

<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.webServer>
<httpProtocol>
<customHeaders>
<!--
Remove unnecesary headers
"no-disallowed-headers": https://webhint.io/docs/user-guide/hints/hint-no-disallowed-headers
-->
<remove name="Public-Key-Pins"/>
<remove name="Public-Key-Pins-Report-Only"/>
<remove name="X-Powered-By"/>
<remove name="X-Runtime"/>
<remove name="X-Version"/>
<!-- Security headers ("strict-transport-security") -->
<add name="Strict-Transport-Security" value="max-age=31536000"/>
</customHeaders>
</httpProtocol>
<!--
This removes the "Server" header on IIS 10 and later
"no-disallowed-headers": https://webhint.io/docs/user-guide/hints/hint-no-disallowed-headers
-->
<security>
<requestFiltering removeServerHeader ="true" />
</security>

<!--
For the dynamic parts of the site that can't be compressed ahead of time. E.g.:
* JSON responses from the server
* dynamically generated html
* ...
-->
<urlCompression doStaticCompression="true" doDynamicCompression="true" dynamicCompressionBeforeCache="false" />
<staticContent>
<!--
Set the mimeType for all the types used in the site. IIS supports a few of
those but they not always have the right values.

Also set `cache-control: no-cache` by default. This will be overriden based
on the file's path.
See https://docs.microsoft.com/en-us/iis/configuration/system.webserver/staticcontent/clientcache
for more info
"content-type": https://webhint.io/docs/user-guide/hints/hint-content-type
-->
<clientCache cacheControlMode="DisableCache" />
<!-- The brotli mime type is unknown to IIS, we need it or otherwise files will not be served correctly -->
<remove fileExtension=".br" />
<mimeMap fileExtension=".br" mimeType="application/brotli" />
<!-- IIS doesn't set the right charset for text types -->
<remove fileExtension=".css"/>
<mimeMap fileExtension=".css" mimeType="text/css; charset=utf-8"/>
<remove fileExtension=".html" />
<mimeMap fileExtension=".html" mimeType="text/html; charset=utf-8" />
<remove fileExtension=".js"/>
<mimeMap fileExtension=".js" mimeType="text/javascript; charset=utf-8"/>
<remove fileExtension=".json"/>
<mimeMap fileExtension=".json" mimeType="application/json; charset=utf-8"/>
<remove fileExtension=".svg"/>
<mimeMap fileExtension=".svg" mimeType="image/svg+xml; charset=utf-8"/>
<remove fileExtension=".txt" />
<mimeMap fileExtension=".txt" mimeType="text/plain; charset=utf-8" />
<remove fileExtension=".xml"/>
<mimeMap fileExtension=".xml" mimeType="text/xml; charset=utf-8"/>
<remove fileExtension=".webmanifest"/>
<mimeMap fileExtension="webmanifest" mimeType="application/manifest+json; charset=utf-8"/>
<!-- font types -->
<remove fileExtension=".woff"/>
<mimeMap fileExtension=".woff" mimeType="font/woff"/>
<remove fileExtension=".woff2"/>
<mimeMap fileExtension=".woff2" mimeType="font/woff2"/>
</staticContent>

<rewrite>
<rewriteMaps>
<!--
* pre-compressed files will be suffixed with br or gz
* map of correct mime types to be restored

"http-compression": https://webhint.io/docs/user-guide/hints/hint-http-compression
-->
<rewriteMap name="CompressedExtensions" defaultValue="">
<add key="css.gz" value="text/css; charset=utf-8" />
<add key="html.gz" value="text/html; charset=utf-8" />
<add key="ico.gz" value="image/x-icon" />
<add key="js.gz" value="text/javascript; charset=utf-8" />
<add key="map.gz" value="application/json; charset=utf-8" />
<add key="svg.gz" value="image/svg+xml; charset=utf-8" />
<add key="txt.gz" value="text/plain; charset=utf-8" />
<add key="xml.gz" value="text/xml; charset=utf-8" />
<add key="webmanifest.gz" value="application/manifest+json; charset=utf-8" />
<add key="css.br" value="text/css; charset=utf-8" />
<add key="html.br" value="text/html; charset=utf-8" />
<add key="ico.br" value="image/x-icon" />
<add key="js.br" value="text/javascript; charset=utf-8" />
<add key="map.br" value="application/json; charset=utf-8" />
<add key="svg.br" value="image/svg+xml; charset=utf-8" />
<add key="txt.br" value="text/plain; charset=utf-8" />
<add key="xml.br" value="text/xml; charset=utf-8" />
<add key="webmanifest.br" value="application/manifest+json; charset=utf-8" />
</rewriteMap>
</rewriteMaps>
<outboundRules>
<!--Restore the mime type for compressed assets. See below for more explanation ("http-compression") -->
<rule name="RestoreMime" enabled="true">
<match serverVariable="RESPONSE_Content_Type" pattern=".*" />
<conditions>
<add input="{HTTP_URL}" pattern="\.((?:css|html|ico|js|map|svg|txt|xml|webmanifest)\.(gz|br))" />
<add input="{CompressedExtensions:{C:1}}" pattern="(.+)" />
</conditions>
<action type="Rewrite" value="{C:3}" />
</rule>

<!-- Remove X-Content-Type from everywhere but JS and CSS ("x-content-type-options") -->
<rule name="X-Content-Type-Options" enabled="true">
<match serverVariable="RESPONSE_X_Content_Type_Options" pattern=".*" />
<conditions>
<add input="{RESPONSE_Content_Type}" pattern="text/(javascript|css)" />
</conditions>
<action type="Rewrite" value="nosniff"/>
</rule>

<!--
Add vary header
"http-compression": https://webhint.io/docs/user-guide/hints/hint-http-compression
-->
<rule name="AddVaryAcceptEncoding" preCondition="PreCompressedFile" enabled="true">
<match serverVariable="RESPONSE_Vary" pattern=".*" />
<action type="Rewrite" value="Accept-Encoding" />
</rule>

<!--
Indicate response is encoded with brotli
"http-compression": https://webhint.io/docs/user-guide/hints/hint-http-compression
-->
<rule name="AddEncodingBrotli" preCondition="PreCompressedBrotli" enabled="true" stopProcessing="true">
<match serverVariable="RESPONSE_Content_Encoding" pattern=".*" />
<action type="Rewrite" value="br" />
</rule>

<!--
Indicate response is encoded with gzip
"http-compression": https://webhint.io/docs/user-guide/hints/hint-http-compression
-->
<rule name="AddEncodingZopfli" preCondition="PreCompressedZopfli" enabled="true" stopProcessing="true">
<match serverVariable="RESPONSE_Content_Encoding" pattern=".*" />
<action type="Rewrite" value="gzip" />
</rule>

<!--
The preconditions to know if a file is compressed and using what algorithm
"http-compression": https://webhint.io/docs/user-guide/hints/hint-http-compression
-->
<preConditions>
<preCondition name="PreCompressedFile">
<add input="{HTTP_URL}" pattern="\.((?:css|html|ico|js|map|svg|txt|xml|webmanifest)\.(gz|br))" />
</preCondition>
<preCondition name="PreCompressedZopfli">
<add input="{HTTP_URL}" pattern="\.((?:css|html|ico|js|map|svg|txt|xml|webmanifest)\.gz)" />
</preCondition>
<preCondition name="PreCompressedBrotli">
<add input="{HTTP_URL}" pattern="\.((?:css|html|ico|js|map|svg|txt|xml|webmanifest)\.br)" />
</preCondition>
</preConditions>
</outboundRules>
<rules>

<!--
Redirect to HTTPS
"https-only": https://webhint.io/docs/user-guide/hints/hint-https-only
-->
<rule name="HTTPSRedirect" stopProcessing="true">
<match url="(.*)"/>
<conditions>
<add input="{HTTPS}" pattern="off" ignoreCase="true"/>
</conditions>
<action type="Redirect" url="https://{HTTP_HOST}{REQUEST_URI}" redirectType="Permanent"
appendQueryString="true"/>
</rule>

<!--
Compression rules. This works in combination with the `outbound rules` bellow. Basically what happens is:

1. Check if the user agent supprots compression via the `Accept-Encoding` header.
2. Prioritize `brotli` over `gzip`, and append the right extension (`.gz` or `.br`) and prepend `dist`.
`dist` is where all the pulic assets live. This is transparent to the user.
Assume all assets with those extensions have a `.gz` and `.br` version.
IIS then serves the asset applying the outbound rules.
3. If the final part of the file (`.ext.gz` or `.ext.br`) matches one of the `CompressedExtensions`
`rewriteMap`, rewrite the `content-type` header
4. Based on the extension (`.gz` or `.br`), rewrite the `content-encoding` header

"http-compression": https://webhint.io/docs/user-guide/hints/hint-http-compression
-->
<rule name="ServePrecompressedBrotli" stopProcessing="true">
<match url="^(.*/)?(.*?)\.(css|html|ico|js|map|svg|txt|xml|webmanifest)([?#].*)?$" ignoreCase="true"/>
<conditions>
<add input="{HTTP_ACCEPT_ENCODING}" pattern="br" negate="false" />
</conditions>
<action type="Rewrite" url="dist{REQUEST_URI}.br"/>
</rule>
<rule name="ServePrecompressedZopfli" stopProcessing="true">
<match url="^(.*/)?(.*?)\.(css|html|ico|js|map|svg|txt|xml|webmanifest)([?#].*)?$" ignoreCase="true"/>
<conditions>
<add input="{HTTP_ACCEPT_ENCODING}" pattern="gzip" negate="false"/>
</conditions>
<action type="Rewrite" url="dist{REQUEST_URI}.gz"/>
</rule>

<!--
Assume that all URLs ending in `/` point to an HTML file that exists already and have a gzip and brotli
compressed version as well.
If that is not the case delete the following "HTML" rules.
-->
<rule name="ServeCompressedHTMLBrotli" stopProcessing="true">
<match url="(^(.*\/)$|^$)" />
<conditions>
<add input="{HTTP_ACCEPT_ENCODING}" pattern="br" negate="false" />
</conditions>
<action type="Rewrite" url="dist{REQUEST_URI}index.html.br"/>
</rule>
<rule name="ServeCompressedHTMLZopfli" stopProcessing="true">
<match url="(^(.*\/)$|^$)" />
<conditions>
<add input="{HTTP_ACCEPT_ENCODING}" pattern="gzip" negate="false" />
</conditions>
<action type="Rewrite" url="dist{REQUEST_URI}index.html.gz"/>
</rule>

<!-- Fallback in case the user agent does a request without requesting compression -->
<rule name="ServeUncompressedResource">
<match url=".*$" ignoreCase="true"/>
<action type="Rewrite" url="dist{REQUEST_URI}"/>
</rule>
</rules>
</rewrite>
</system.webServer>
<!--
All the static assets should be under `dist/static`, set a long cache and the `immutable` directive, overriding
the `no-cache` set up earlier.
"http-cache": https://webhint.io/docs/user-guide/hints/hint-http-cache
-->
<location path="dist/static">
<system.webServer>
<staticContent>
<clientCache cacheControlMode="UseMaxAge" cacheControlMaxAge="365.00:00:00" cacheControlCustom="immutable" />
</staticContent>
</system.webServer>
</location>
</configuration>