My WordPress web.config

Do you host your WordPress website on Windows Server IIS? Having trouble with your web.config? I often receive questions about how to use a web.config file in WordPress on Windows Server, and which settings are important for a WordPress website. Maybe it’s because I’m a WordPress on IIS enthusiast, so here is my web.config for your convenience (really, it’s not that special).

WordPress web.config for IIS #

Some web.config settings for Windows Server IIS, PHP or WordPress are specific to my hosting environment. They may cause errors in your set-up. Where possible, web.config settings are commented. I’ve removed some very site specific URL rewrites, and for security reasons paths to files are removed.

If you have questions about a specific web.config setting, don’t hesitate to leave a comment. However, do contact your own hosting provider for support on what’s supported in your hosting environment.

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <configSections>
    <!--
      Load HttpBL assembly to keep suspicious and malicious web robots
      off my sites. Get your Access Key
      @ www.projecthoneypot.org/create_account.php

      For more information about Project Honey Pot, see:
      www.saotn.org/filter-web-traffic-with-blacklists/
      www.saotn.org/project-honey-pot/
    -->
    <section name="HttpBL" type="HttpBL.Settings" />
  </configSections>
  <appSettings/>

  <!--
    Configure HttpBL settings, choose what is best for your situation
  -->
  <HttpBL
    Enabled="true"
    AlwaysAllow=""
    AlwaysDeny=""
    AccessKey="xyzabc"
    QueryDomain="dnsbl.httpbl.org"
    MaxAge="30"
    MaxScore="40"
    CacheTTL="7200"
    CacheWhite="true"
    RedirectOnHit="false"
    RedirectURL="/denied.aspx?ip=$IP&amp;result=$RESULT"
    Logging="false"
    LogPath="\path\to\HttpBL\logfile"
    LogHits="false" />

  <system.webServer>
  <modules>
    <!--
      If installed (server wide), remove the Helicon Ape module because 
      the module can eat quite a bit of RAM per worker process
    -->
    <remove name="Helicon.Ape" />

    <!--
      Add the HttpBL .NET module
    -->
    <add name="HttpBL" type="HttpBL.HttpBL" />

    <!--
      IIS caching modules for URI-, file- and authentication tokens
    -->
    <add name="UriCacheModule" />
    <add name="FileCacheModule" />
    <add name="TokenCacheModule" />
  </modules>

  <!--
    We need to set a mimeType for javascrip there, so configure some 
    other types too. Notice minFileSizeForComp, this specifies the 
    minimum number of kilobytes a file must contain in order to use 
    on-demand compression
  -->
  <httpCompression minFileSizeForComp="0">
    <scheme
      name="gzip"
      dll="%Windir%\system32\inetsrv\gzip.dll"
      staticCompressionLevel="7" />
    <dynamicTypes>
      <clear/>
      <add mimeType="text/*" enabled="true" />
      <add mimeType="message/*" enabled="true" />
      <add mimeType="application/x-javascript" enabled="true" />
      <add mimeType="*/*" enabled="false" />
      <add mimeType="image/svg+xml" enabled="true" />
      <add mimeType="application/font-woff" enabled="true" />
      <add mimeType="application/x-font-ttf" enabled="true" />
      <add mimeType="application/octet-stream" enabled="true" />
    </dynamicTypes>
    <staticTypes>
      <clear/>
      <add mimeType="text/*" enabled="true" />
      <add mimeType="message/*" enabled="true" />
      <add mimeType="application/x-javascript" enabled="true" />
      <add mimeType="application/atom+xml" enabled="true" />
      <add mimeType="application/xaml+xml" enabled="true" />
      <add mimeType="*/*" enabled="false" />
      <add mimeType="image/svg+xml" enabled="true" />
      <add mimeType="application/font-woff" enabled="true" />
      <add mimeType="application/x-font-ttf" enabled="true" />
      <add mimeType="application/octet-stream" enabled="true" />
    </staticTypes>
  </httpCompression>
  <!--
    urlCompression can give issues under certain circumstances
  -->
  <urlCompression
    doStaticCompression="true"
    doDynamicCompression="true"
    dynamicCompressionBeforeCache="true" />
  
  <!--
    Browser cache (or client cache), and mimeMappings for IIS
  -->
  <staticContent>
    <clientCache cacheControlMode="UseMaxAge" cacheControlMaxAge="28.00:00:00" />
    <remove fileExtension=".html" />
    <mimeMap fileExtension=".html" mimeType="text/html;charset=UTF-8" />
    <remove fileExtension=".css" />
    <mimeMap fileExtension=".css" mimeType="text/css" />
    <remove fileExtension=".htm" />
    <mimeMap fileExtension=".htm" mimeType="text/html;charset=UTF-8" />
    <remove fileExtension=".woff" />
    <mimeMap fileExtension=".woff" mimeType="application/font-woff" />
    <remove fileExtension=".js" />
    <mimeMap fileExtension=".js" mimeType="application/x-javascript;charset=UTF-8" />
    <remove fileExtension=".svg" />
    <mimeMap fileExtension=".svg" mimeType="image/svg+xml" />
  </staticContent>

  <!--
    Remove all in IIS configured defaultDocuments, and
    add the ones that are necessary. This speeds up finding the defaultDocument.
  -->
  <defaultDocument>
    <files>
      <clear/>
      <add value="index.php" />
      <add value="index.html" />
    </files>
  </defaultDocument>

  <!--
    Remove and add some response headers
  -->
  <httpProtocol>
    <customHeaders>
      <remove name="X-Powered-By" />
      <remove name="Vary" />
      <add name="Access-Control-Allow-Origin" value="*" />
      <add name="X-UA-Compatible" value="IE=Edge,chrome=1" />
    </customHeaders>
  </httpProtocol>

  <handlers>
    <!--
      Remove the existing PHP fastCgi handler, so we can add our own
    -->
    <remove name="PHP" />
  
    <!--
      My PHP7 WinCache PHP handler in IIS, the scriptProcessor path is 
      specific to my environment. Due to a file system cache bug in 
      WinCache v1.3.7.4 for PHP 5.6, I'm running PHP 7/WinCache.
      
      See @ www.saotn.org/php-wincache-on-iis/ for more
      PHP WinCache configuration information
    -->
    <add name="PHP"
      path="*.php"
      verb="*"
      modules="FastCgiModule"
      scriptProcessor="\path\to\php7\php-cgi.exe|-c \path\to\php7\php.wincache.ini"
      resourceType="File"
      allowPathInfo="true"
      requireAccess="Script"
      responseBufferLimit="0" />
  </handlers>
  <!--
    Here we configure URL rewrites. For example, we can block referers,
    block access to wp-comments-post.php or wp-login.php, and all our WordPress 
    rewrites go here.
  -->
  <rewrite>

      <!--
        Block out some known spam referrers
      -->
      <rule name="block_spam_referrers" stopProcessing="true">
        <matchurl="(.*)" ignoreCase="true" />
        <conditions logicalGrouping="MatchAny">
          <add input="{HTTP_REFERER}" pattern="https?://(www\.)?make-money-online\.7makemoneyonline\.com.*" negate="false" />
          <add input="{HTTP_REFERER}" pattern="https?://(www\.)?buttons-for-your-website\.com.*" negate="false" />
          <add input="{HTTP_REFERER}" pattern="https?://(www\.)?buttons-for-website\.com.*" negate="false" />
          <add input="{HTTP_REFERER}" pattern="https?://(www\.)?ranksonic\.info.*" negate="false" />
          <add input="{HTTP_REFERER}" pattern="https?://(www\.)?youmaydownloadthem\.com.*" negate="false" />
          <add input="{HTTP_REFERER}" pattern="https?://(www\.)?o-o-6-o-o\.com.*" negate="false" />
          <add input="{HTTP_REFERER}" pattern="https?://(www\.)?realforexgeminicodereviews\.com.*" negate="false" />
          <add input="{HTTP_REFERER}" pattern="https?://s\.click\.aliexpress\.com.*" negate="false" />
          <add input="{HTTP_REFERER}" pattern="https?://(www\.)?androidfirmware\.science.*" negate="false" />
          <add input="{HTTP_REFERER}" pattern="https?://(www\.)?best-seo-offer\.com.*" negate="false" />
          <add input="{HTTP_REFERER}" pattern="https?://(www\.)?best-seo-solution\.com.*" negate="false" />
          <add input="{HTTP_REFERER}" pattern="https?://(www\.)?cenoval\.ru.*" negate="false" />
          <add input="{HTTP_REFERER}" pattern="https?://(www\.)?pornhub-forum\.ga.*" negate="false" />
          <add input="{HTTP_REFERER}" pattern="https?://(www\.)?buy-cheap-online\.info.*" negate="false" />
          <add input="{HTTP_REFERER}" pattern="https?://(www\.)?get-free-traffic-now\.com.*" negate="false" />
          <add input="{HTTP_REFERER}" pattern="https?://(www\.)?hulfingtonpost\.com.*" negate="false" />
          <add input="{HTTP_REFERER}" pattern="https?://(www\.)?semalt\.semalt\.com.*" negate="false" />
        </conditions>
        <action type="CustomResponse"
          statusCode="403"
          statusReason="Forbidden: Access is denied."
          statusDescription="Access to this website from the site you came from is prohibited!" />
      </rule>

      <!--
        Here start my WordPress Multisite rewrite rules
      -->
      <rule name="WordPress RewriteRule 1" stopProcessing="true">
        <match url="^index\.php$" ignoreCase="false" />
        <action type="None" />
      </rule>
      <rule name="WordPress RewriteRule 2" stopProcessing="true">
        <match url="^wp-admin$" ignoreCase="false" />
        <action type="Redirect" url="wp-admin/" redirectType="Permanent" />
      </rule>
      <rule name="WordPress RewriteRule 3" stopProcessing="true">
        <match url="^" ignoreCase="false" />
        <conditions logicalGrouping="MatchAny">
          <add input="{REQUEST_FILENAME}" matchType="IsFile"ignoreCase="false" />
          <add input="{REQUEST_FILENAME}" matchType="IsDirectory"ignoreCase="false" />
        </conditions>
       <action type="None" />
      </rule>
      <rule name="WordPress RewriteRule 4" stopProcessing="true">
        <match url="^(wp-(content|admin|includes).*)" ignoreCase="false" />
        <action type="Rewrite" url="{R:1}" />
      </rule>
      <rule name="WordPress RewriteRule 5" stopProcessing="true">
        <match url="^([_0-9a-zA-Z-]+/)?(.*\.php)$" ignoreCase="false" />
        <action type="Rewrite" url="{R:2}" />
      </rule>
      <rule name="WordPress RewriteRule 6" stopProcessing="true">
        <match url="."ignoreCase="false" />
        <action type="Rewrite" url="index.php" />
      </rule>
      <!--
        WordPress Permalinks URL Rewrite
        Disabled in favor of WordPress Multisite
      -->
      <!--
      <rule name="wordpress" patternSyntax="Wildcard">
        <match url="*" />
        <conditions>
          <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
          <add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
        </conditions>
        <action type="Rewrite" url="index.php" />
      </rule>
      -->
    </rules>
    <outboundRules>
      <!--
        Remove Server response header
        www.saotn.org/remove-iis-server-version-http-response-header/
      -->
      <rule name="Remove Server header">
        <match serverVariable="RESPONSE_Server" pattern=".+" />
        <action type="Rewrite" value="" />
      </rule>
      <!--
        Configure HSTS for HTTPS
        www.saotn.org/enable-http-strict-transport-security-hsts-on-iis/
      -->
      <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" />
      </rule>
    </outboundRules>
  </rewrite>

  <!-- 
    Block out some known offending IP addresses. Unfortunately, it is almost
    impossible to keep this up-to-date
  -->
  <security>
    <ipSecurity>
      <add ipAddress="193.201.224.96" allowed="false" />
      <add ipAddress="185.19.92.163" allowed="false" />
      <add ipAddress="37.128.149.238" allowed="false" />
      <add ipAddress="37.59.151.190" allowed="false" />
      <add ipAddress="176.10.104.96" allowed="false" />
      <add ipAddress="202.6.19.50" allowed="false" />
      <add ipAddress="178.162.209.133" allowed="false" />
      <add ipAddress="178.162.205.23" allowed="false" />
      <add ipAddress="155.133.18.127" allowed="false" />
      <add ipAddress="190.172.12.239" allowed="false" />
      <add ipAddress="195.154.235.59" allowed="false" />
      <add ipAddress="195.154.232.169" allowed="false" />
      <add ipAddress="62.210.140.103" allowed="false" />
      <add ipAddress="87.66.111.150" allowed="false" />
      <add ipAddress="175.126.100.17" allowed="false" />
      <add ipAddress="103.23.201.170" allowed="false" />
      <add ipAddress="202.164.234.1" allowed="false" />
    </ipSecurity>

    <!--
      IIS Request Filtering rules.
      Block out some requests to known backdoors (or vulnerable scripts). 
      Watch out: names can vary...
    -->
    <requestFiltering>
      <denyUrlSequences>
        <add sequence="ofc_upload_image.php" />
        <add sequence="timthumb.php" />
        <add sequence="img.php" />
        <add sequence="img_x.php" />
        <add sequence="thumb.php" />
        <add sequence="phpthumb.php" />
        <add sequence="kontol.php" />
        <add sequence="magic.php.png" />
        <add sequence="food.php" />
        <add sequence="ph.php" />
        <add sequence="fragile.php" />
        <add sequence="3xp.php" />
        <add sequence="explore.php" />
        <add sequence="petx.php" />
        <add sequence="dl-skin.php" />
        <add sequence="direct_download.php" />
        <add sequence="getfile.php" />
        <add sequence="vito.php" />
        <add sequence="upload_settings_image.php" />
        <add sequence="saint.php" />
        <add sequence="lunar.php" />
        <add sequence="nyet.gif" />
        <!-- /& URI -->
        <add sequence="/&amp;" />
        <add sequence="/login.php" />
        <add sequence="magmi.php" />
      </denyUrlSequences>

      <!--
        Yes, even my WordPress site gets scanned for Joomla 
        com_jce vulnerabilities... www.saotn.org/joomla-sites-misused-deploy-malware/
      -->
      <denyQueryStringSequences>
        <add sequence="option=com_jce&amp;task=plugin&amp;plugin=imgmanager&amp;file=imgmanager&amp;version=1576&amp;cid=20" />
        <!--
          You can add Query String sequences below, for example to (try to) block some SQL injection
          or Cross Site Scripting attacks, but only through HTTP GET:
        -->
        <add sequence="action=revslider_show_image&amp;img=../wp-config.php" />
      </denyQueryStringSequences>

      <!--
        Block SQL injection attacks through IIS Request Filtering filtering Rules.
        These are merely examples to show you the power of IIS and Request Filtering
        www.iis.net/configreference/system.webserver/security/requestfiltering/filteringrules
      -->
      <filteringRules>
        <filteringRule name="prevent SQL injection"
          scanUrl="true"
          scanQueryString="true">
          <appliesTo>
            <clear />
            <add fileExtension=".php" />
          </appliesTo>
          <denyStrings>
            <add string="@" />
            <add string="select" />
            <add string="table" />
            <add string="update" />
            <add string="--" />
            <!-- ... -->
            <!-- ... -->
          </denyStrings>
      </filteringRules>
    </requestFiltering>
  </security>
</system.webServer>

  <!--
    WordPress wp-login.php security: IP address whitelist,
    all IP addresses not listed below are denied access to /wp-login.php.
    IIS IP and Domain Restrictions - or IP Security - module
  -->
  <location path="wp-login.php">
    <system.webServer>
      <security>
	<!-- this line blocks all IP addresses, except those listed below -->
        <ipSecurity allowUnlisted="false">
          <add ipAddress="203.0.113.15" allowed="true" />
          <add ipAddress="203.0.113.16" allowed="true" />
        </ipSecurity>
      </security>
    </system.webServer>
  </location>

  <!--
    Disable PHP execution in WordPress uploads folder, for extra security. See
    @ www.saotn.org/secure-wordpress-wp-contentuploads-folder-disable-php-execution/
  -->
  <location path="wp-content/uploads">
    <system.webServer>
      <handlers accessPolicy="Read"/>
    </system.webServer>
  </location>
</configuration>

Extra security measurements
Want to learn about more security measurements you can take to secure WordPress on Windows Server? Have a look at:

This may interest you:   A plea for WordPress plugin developers to stop supporting legacy PHP versions

HackRepair.com's Bad Bots .htaccess translated to web.config, for IIS. Or learn to enable HSTS Strict-Transport-Security header in IIS, how to remove IIS Server header or add SSL in WordPress.


Show your support


If you want to step in to help me cover the costs for running this website, that would be awesome. Just use this link to donate a cup of coffee ($5 USD for example). And please share the love and help others make use of this website. Thank you very much!


About the Author Jan Reilink

My name is Jan. I am not a hacker, coder, developer, programmer or guru. I am merely a system administrator, doing my daily thing at Vevida in the Netherlands. With over 15 years of experience, my specialties include Windows Server, IIS, Linux (CentOS, Debian), security, PHP, websites & optimization.

follow me on:

Leave a Reply

17 Comments on "My WordPress web.config"

avatar
  Subscribe  
newest oldest most voted
Notify of
Chris
Guest

There looks to be a typo in the section to block SQL injection attacks on line #344:

scanUlr="true"

Judging by the config reference on iis.net this should be:

scanUrl="true"
Roy
Guest
Jan, I’m working on a WP multi site at Vevida. My first experience with a Windows box… One thing I badly want to accomplish is blocking direct access to uploads used in the members-only part of the website. I have made a members only subsite, so basically I need to block direct access to most files from that site (except the header image of course). I ran into different methods to accomplish this, but the easiest way seem to be using web.config, like .htaccess on a Linux server. Should I edit the web.config in the root or rather create a… Read more »
Roy
Guest

The Vevida servicedesk doesn’t know why this wouldn’t work. I’ve done some further experimenting with .htaccess and web.config but all to no avail. Suggestions as to in what direction to look for a sollution would be welcome :-) (But perhaps not through this comments thread? It is starting to look like a helpdesk forum about a problem that is not even subject of the article above.)

Bob
Guest

This section needed spaces inserting between ” and mime, lines 108, 110, 112, 114, 116, 118, and line 140 needed a space before value

Devin Columbus
Guest

Thanks for this. Can you make this entire file downloadable? I feel like I’m missing some code or the order of it all by copy and pasting each code section.

web cancún
Guest

Hi!
Good job!
I am working with wordpress multisite, and when configuring the web.config file, I get a 500 error,
The code I have is the following:

Fill in your details below or click an icon to log in:

<system.webServer>

</system.webServer>

Please help me …