How to configure Internet Information Server (IIS) to improve the performance and SEO of your website

Juan Pedro Catalá

Written by Juan Pedro Catalá

Optimally configuring the web server on which you host your website is one of the key factors in the performance of your website. Correctly setting the parameters for compressing and caching files can significantly improve the loading speed of your page.

In this article I will explain how to configure Internet Information Server (IIS), one of the most used web servers nowadays together with Apache and Nginx.

For it I am going to create the web.config file, which is the file in which the configuration of an ASP.NET application is specified. This file must always go in the root of our ASP.NET application.

In this case, we assume that we have IIS version 7.5 and ASP.NET version 4.5, although most things would also work in ASP.NET 4.

Next, I specify the XML web.config file with the commented options.

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <appSettings>
    <!-- Estos parámetros de appSettings solo funcionan en ASP.NET 4.5 o superior -->
    <add key="ClientValidationEnabled" value="true" />
    <!-- Con está opción especificamos que ASP.NET utilice la validación no intrusiva, evitando que inserte código de javascript en linea en todas las páginas de nuestra web -->
    <add key="UnobtrusiveJavaScriptEnabled" value="true" />
  </appSettings>
  <!-- Configuración de ASP.NET -->
  <system.web>
    <!-- 
      Especificamos el framework que utilizamos (4.5)
      El lenguaje de programación (C#)
      Que no compile la aplicación entera cada vez que hagamos un cambio en el código.
      Especificamos que utilice la compilación por lotes y el tamaño máximo de los ensamblados generados al compilar. Esto suele funcionar bien para aplicaciones pequeñas y medianas pero puede dar problemas en aplicaciones con un gran número de archivos, se debe probar y ver la forma en la que la aplicación funciona mejor.
      Establecemos el número máximo de veces que la aplicación puede recompilarse parcialmente antes de reiniciar completamente. Esto es muy útil si no se hacen cambios muy a menudo en la web ya que evitamos que la aplicación tenga que cargar de nuevo todo el código compilado en memoria siendo los cambios transparentes para los visitantes. Si se hacen varios cambios diariamente es mejor establecer está opción a un número menor como 5, para evitar consumir memoria de forma excesiva.
      Especificamos que la aplicación no se ejecute en modo depuración.
      -->
      <compilation targetFramework="4.5" defaultLanguage="C#" optimizeCompilations="true" batch="true" maxBatchGeneratedFileSize="2147483647" maxBatchSize="2147483647" numRecompilesBeforeAppRestart="2147483647" debug="false" />

      <!-- Especificamos un nombre de cookie de sesión corto y habilitamos la compresión, esta opción es muy útil sobre todo utilizamos el modo en el que la sesión se guarda en memoria (InProc) -->
      <sessionState timeout="1440" cookieName="s" compressionEnabled="true" />

      <!-- Eliminamos la cabecera de la versión de ASP.NET para mayor seguridad -->
      <httpRuntime targetFramework="4.5" requestValidationMode="2.0" requestPathInvalidCharacters="" enableVersionHeader="false" relaxedUrlToFileSystemMapping="true" />

<!-- Especificamos que las cookies enviadas no se puedan modificar desde javascript -->
<httpCookies httpOnlyCookies="true" requireSSL="false" />

        <!-- Especificamos el idioma y la codificación de los archivos por defecto. -->
        <globalization culture="es-ES" uiCulture="es-ES" enableBestFitResponseEncoding="false" enableClientBasedCulture="false" fileEncoding="UTF-8" requestEncoding="UTF-8" responseEncoding="UTF-8" responseHeaderEncoding="UTF-8" />

        <!-- 
        (clientIdMode) Especificamos que los IDs de los controles se generen con el algoritmo "Predictable" para que sean lo más cortos posibles.
        (autoEventWireUp) Evitamos que ASP.NET examine la página para asignar los eventos de los controles.
        (renderAllHiddenFieldsAtTopOfForm) Con está opción en false evitamos que en los webforms aparezcan todos los hidden en la parte superior de la página.
        -->
        <pages clientIDMode="Predictable" autoEventWireup="false" enableEventValidation="true" buffer="true" compilationMode="Always" enableSessionState="true" enableViewState="true" enableViewStateMac="true" validateRequest="true" renderAllHiddenFieldsAtTopOfForm="false" viewStateEncryptionMode="Always"></pages>

       <!--
       Especificamos que se utilice la reescritura de la respuesta cuando se produce un error en la aplicación en vez de hacer una redirección. De esta forma logramos que los códigos de estado devueltos sean correctos.
       -->
       <customErrors mode="RemoteOnly" redirectMode="ResponseRewrite" defaultRedirect="error.aspx?error=400">
         <error statusCode="404" redirect="error.aspx?error=404" />
         <error statusCode="500" redirect="error.aspx?error=500" />
       </customErrors>

       <!-- Deshabilitamos el seguimiento -->
       <trace enabled="false" />
  </system.web>

  <!-- Habilitamos la compresión y las cabeceras de cache en los manejadores AJAX de ASP.NET -->
  <system.web.extensions>
    <scripting>
      <scriptResourceHandler enableCaching="true" enableCompression="true" />
    </scripting>
  </system.web.extensions>

  <!-- Configuración de IIS -->
  <system.webServer>
<!-- Habilitamos la compresión GZIP y especificamos las cabeceras de cache a un año -->
        <urlCompression doStaticCompression="true" doDynamicCompression="true" dynamicCompressionBeforeCache="true" />
<httpCompression directory="%SystemDrive%\inetpub\temp\IIS Temporary Compressed Files" sendCacheHeaders="true" cacheControlHeader="max-age=31536000" dynamicCompressionDisableCpuUsage="90" staticCompressionDisableCpuUsage="90" dynamicCompressionEnableCpuUsage="0" staticCompressionEnableCpuUsage="0">
  <scheme name="gzip" dll="%Windir%\system32\inetsrv\gzip.dll" doStaticCompression="true" doDynamicCompression="true" dynamicCompressionLevel="10" staticCompressionLevel="10" />
  <staticTypes>
    <add mimeType="text/*" enabled="true" />
            <add mimeType="message/*" enabled="true" />
            <add mimeType="application/javascript" enabled="true" />
            <add mimeType="application/x-javascript" enabled="true" />
            <add mimeType="application/json" enabled="true" />
            <add mimeType="*/*" enabled="false" />
  </staticTypes>
          <dynamicTypes>
            <add mimeType="text/*" enabled="true" />
            <add mimeType="message/*" enabled="true" />
            <add mimeType="application/javascript" enabled="true" />
            <add mimeType="application/x-javascript" enabled="true" />
            <add mimeType="application/json" enabled="true" />
            <add mimeType="*/*" enabled="false" />
          </dynamicTypes>
</httpCompression>

        <!-- Especificamos que se utilice el reemplazo de la respuesta cuando se produce un error en el servidor en vez de hacer una redirección. De está forma logramos que los códigos de estado devueltos sean correctos. -->
<httpErrors existingResponse="Replace" errorMode="Custom">
  <remove statusCode="404" subStatusCode="-1" />
  <error statusCode="404" subStatusCode="-1" path="/error.aspx?error=404" responseMode="ExecuteURL" />
  <remove statusCode="500" subStatusCode="-1" />
  <error statusCode="500" subStatusCode="-1" path="/error.aspx?error=500" responseMode="ExecuteURL" />
</httpErrors>

        <!-- Deshabilitamos el listado de directorios -->
<directoryBrowse enabled="false" />

        <!-- Deshabilitamos la validación de la configuración en modo integrado -->
<validation validateIntegratedModeConfiguration="false" />

<!-- Deshabilitamos que todos los módulos se ejecuten en todas las peticiones -->
<modules runAllManagedModulesForAllRequests="false" />

        <!-- 
        Establecemos los tipos MIME de todos los archivos que servimos.
        IIS no sirve los archivos que desconoce su tipo MIME por seguridad.
        Establecemos la cache en un año del cliente.
        -->
<staticContent>
          <clientCache cacheControlMode="UseMaxAge" cacheControlMaxAge="365.00:00:00" cacheControlCustom="public" />
  <remove fileExtension=".css" />
  <mimeMap fileExtension=".css" mimeType="text/css" />
  <remove fileExtension=".js" />
  <mimeMap fileExtension=".js" mimeType="application/javascript" />
  <remove fileExtension=".json" />
  <mimeMap fileExtension=".json" mimeType="application/json" />
  <remove fileExtension=".rss" />
  <mimeMap fileExtension=".rss" mimeType="application/rss+xml; charset=UTF-8" />
  <remove fileExtension=".html" />
  <mimeMap fileExtension=".html" mimeType="text/html; charset=UTF-8" />
  <remove fileExtension=".xml" />
  <mimeMap fileExtension=".xml" mimeType="application/xml; charset=UTF-8" />
  <remove fileExtension=".mp3" />
  <mimeMap fileExtension=".mp3" mimeType="audio/mpeg" />
  <remove fileExtension=".mp4" />
  <mimeMap fileExtension=".mp4" mimeType="video/mp4" />
  <remove fileExtension=".ogg" />
  <mimeMap fileExtension=".ogg" mimeType="audio/ogg" />
  <remove fileExtension=".ogv" />
  <mimeMap fileExtension=".ogv" mimeType="video/ogg" />
  <remove fileExtension=".webm" />
  <mimeMap fileExtension=".webm" mimeType="video/webm" />
  <remove fileExtension=".svg" />
  <mimeMap fileExtension=".svg" mimeType="image/svg+xml" />
  <remove fileExtension=".svgz" />
  <mimeMap fileExtension=".svgz" mimeType="image/svg+xml" />
  <remove fileExtension=".eot" />
  <mimeMap fileExtension=".eot" mimeType="application/vnd.ms-fontobject" />
  <remove fileExtension=".ttf" />
  <mimeMap fileExtension=".ttf" mimeType="application/x-font-ttf" />
  <remove fileExtension=".ttc" />
  <mimeMap fileExtension=".ttc" mimeType="application/x-font-ttf" />
  <remove fileExtension=".otf" />
  <mimeMap fileExtension=".otf" mimeType="font/opentype" />
  <remove fileExtension=".woff" />
  <mimeMap fileExtension=".woff" mimeType="application/font-woff" />
  <remove fileExtension=".crx" />
  <mimeMap fileExtension=".crx" mimeType="application/x-chrome-extension" />
  <remove fileExtension=".xpi" />
  <mimeMap fileExtension=".xpi" mimeType="application/x-xpinstall" />
  <remove fileExtension=".safariextz" />
  <mimeMap fileExtension=".safariextz" mimeType="application/octet-stream" />
  <remove fileExtension=".flv" />
  <mimeMap fileExtension=".flv" mimeType="video/x-flv" />
  <remove fileExtension=".f4v" />
  <mimeMap fileExtension=".f4v" mimeType="video/mp4" />
  <remove fileExtension=".ico" />
  <mimeMap fileExtension=".ico" mimeType="image/x-icon" />
  <remove fileExtension=".webp" />
  <mimeMap fileExtension=".webp" mimeType="image/webp" />
  <remove fileExtension=".htc" />
  <mimeMap fileExtension=".htc" mimeType="text/x-component" />
  <remove fileExtension=".vcf" />
  <mimeMap fileExtension=".vcf" mimeType="text/x-vcard" />
  <remove fileExtension=".torrent" />
  <mimeMap fileExtension=".torrent" mimeType="application/x-bittorrent" />
  <remove fileExtension=".cur" />
  <mimeMap fileExtension=".cur" mimeType="image/x-icon" />
</staticContent>
<httpProtocol>
  <customHeaders>
<!-- Establecemos la cabecera que indica que nuestra página solo puede aparecer en frames o iframes de nuestro propio dominio. -->
                <add name="X-Frame-Options" value="SAMEORIGIN" />

<!-- Habilitamos la protección contra XSS en Internet Explorer. -->
                <add name="X-XSS-Protection" value="1; mode=block"/>

<!-- Habilitamos la protección contra cambios de tipo MIME en Internet Explorer -->
<add name="X-Content-Type-Options" value="nosniff" />

<!-- Eliminamos la cabecera X-Powered-By -->
<remove name="X-Powered-By" />

<!-- 
                Eliminamos la cabecera ETAG. Si un recurso cambia lo mejor es que cambie de URL para que se vuelva a descargar así nos ahorramos que el cliente haga una petición de consulta por cada archivo con la cabecera ETAG
                -->
                <add name="ETAG" value="" />

<!--
                Forzamos a que Internet Explorer use la última versión de su motor de renderizado.
                -->
<add name="X-UA-Compatible" value="IE=Edge" />
  </customHeaders>
</httpProtocol>

        <!-- Establecemos default.aspx como el archivo por defecto de un directorio -->
        <defaultDocument enabled="true">
          <files>
            <clear />
            <add value="default.aspx" />
          </files>
        </defaultDocument>
<!-- 
        Reglas de reescritura de URLs 
        Es necesario tener instalado el módulo "URL Rewrite Module 2.0"
        -->
        <rewrite>
            <rules>
                <!-- Forzamos que el dominio salga siempre con www, excepto cuando ejecutamos el proyecto en local o empieza por www -->
                <rule name="WWW" stopProcessing="true">
                    <match url="(.*)" />
                    <conditions>
                        <add input="{HTTP_HOST}" pattern="(.*)" />
                        <add input="{HTTP_HOST}" pattern="^www\." negate="true" />
                        <add input="{HTTP_HOST}" pattern="^localhost" negate="true" />
                    </conditions>
                    <action type="Redirect" url="http://www.{C:1}/{R:1}" redirectType="Permanent" />
                </rule>

                <!--  Eliminamos default.aspx de la URL -->
                <rule name="Default Document" stopProcessing="true">
                  <match url="^(.*)default\.aspx$" />
                  <action type="Redirect" url="{R:1}" redirectType="Permanent" />
                </rule>

                <!-- Convierte toda la url a minúscula, excepto cuando sea un archivo, un directorio, un manejador AJAX de ASP.NET o un bundle -->
                <rule name="Lower Case URLs" stopProcessing="true">
                  <match url="[A-Z]" ignoreCase="false" />
                    <conditions logicalGrouping="MatchAll" trackAllCaptures="false">
                      <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
                      <add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
                      <add input="{URL}" pattern="^(.+)\.axd$" negate="true" />
                      <add input="{URL}" pattern="^bundles/" negate="true" />
                    </conditions>
                    <action type="Redirect" url="{ToLower:{URL}}" redirectType="Permanent" />
                  </rule>

                <!-- 
                Especificamos regla de reescritura para los bundles de ASP.NET 
                /bundles/css/asd12SDasdada -> /bundles/css?v=asd12SDasdada
                -->
                <rule name="Bundles">
                  <match url="^bundles/([^/]+)/(.+)$" />
                  <action type="Rewrite" url="/bundles/{R:1}?v={R:2}" />
                </rule>

            </rules>
        </rewrite>

  </system.webServer>
</configuration>

 

By performing a measurement before and after making changes to the default values of the web.config file, you will find a great improvement in the loading speed of your web page, something that your visitors will appreciate.

I leave you a link to the configuration file so that you can download it easily.

  •  | 
  • Last modified on
Juan Pedro Catalá
Juan Pedro Catalá
Former senior web developer at Human Level. Graduated in Technical Engineering in Computer Management. Subsequently, he completed a Master's Degree in Web Services and Applications Development. Specialist in web development, e-commerce and booking engines.

What do you think? Leave a comment

Just in case, your email will not be shown ;)

Related Posts

en