My WordPress web.config

Reading Time: 26 Minutes

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.

Advertisement:

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
      @ https://www.projecthoneypot.org/create_account.php

      For more information about Project Honey Pot, see
      
Filter web traffic with blacklists
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 @ https:// 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>

Pro tip: Convert .htaccess to web.config easily with help from this post. Use SSL in WordPress.

  <!--
    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>

Learn the 10 Tips To Optimize Your WordPress hosting – 10 Practical Tips.

    <outboundRules>
      <!--
        Remove Server response header
        https:// 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
        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... https:// 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
        http://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
    @ https://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

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.


Advertisement:

15 Replies to “My WordPress web.config”

  1. 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"
    
  2. 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 new one in the uploads folder of the specific site (www /wp-content /uploads /sites /5) and what should be in it? I am no star in htaccess (I usually manage what I have to in the end), but since this web.config business is even newer to me (getting the network up was quite an ordeal) some suggestions might be helpfull. We are talking about mostly PDFs and handfull of DOCs.

    Thanks in advance.

    1. Hello Roy, thank you for your comment and hosting with Vevida :-)

      Do you want to deny access to all files in /wp-content/uploads/sites/5, or to some files? You can use both .htaccess and web.config files on our hosting platform, so if you’re comfortable with .htaccess you can stick with those. Web.config files have the advantage you can define a directory location in your web.config file in the root, to secure a sub directory (e.g location="wp-content/uploads/sites/5"). Then you’ll only need one web.config file and not multiple.

      To deny access to all files in that directory, using a .htaccess file:

      order deny,allow
      deny from all
      allow from 111.222.333.444

      Where 111.222.333.444 is your IP address so you can still access the files.

      In a web.config file, in wp-content/uploads/sites/5, the following should work to deny access to all anonymous users:

      <?xml version="1.0" encoding="utf-8" ?>
      <configuration>
          <system.web>
              <authorization>
                  <deny users="?" /> 
              </authorization>
          </system.web>
      </configuration>

      Does this help?

      1. Thank you for your reply. Subsite 5 is a members only with different levels of rights. Users should have access to the files and not just me. What I want to accomplish is that nobody can access certain files from the uploads folder outside the website. Of course somebody who is not logged in cannot access the pages, but should he have the direct url to a file in the uploads folder, he can just open it in his browser and that is what I want to prevent.

        So htaccess will work? That could be something to look at indeed. I remember setting up the network was different from what I am used to. Perhaps wp-config works differently on IIS. I do not even remember :-)

        1. htaccess doesn’t do the trick. I actually already knew that actually. htaccess blocks direct access alright, but also disallows access to logged in users. (This seems to be some combination of settings on the IIS server, on a Linux server it works like I expect it to.)

          Do you have an example of a web.config that I can put in the uploads folder (preferably not the web.config in the root) that blocks direct access to pdf files? I cann’t seem to find a good example online.

          1. Hi Roy,

            I think I may have found a solution, based on cookies. Assuming you use a WordPress members solution, where members log in and a cookie is set, the following web.config file blocks access to ALL files if no wordpress_logged_in cookie is set:

            <?xml version="1.0" encoding="UTF-8"?>
            <configuration>
              <system.webServer>
                <rewrite>
                  <rules>
                    <clear />
                    <rule name="Block_files_for_non-logged-in_users"
                      stopProcessing="true">
                      <match url="(.*)" />
                      <conditions trackAllCaptures="true">
                        <add
                          input="{HTTP_COOKIE}"
                          pattern="^.*(wordpress_logged_in).*$"
                          negate="true"
                          ignoreCase="true"
                        />
                      </conditions>
                      <action
                        type="CustomResponse"
                        statusCode="403"
                        statusReason="Forbidden: Access is denied."
                        statusDescription="Access is denied!" />
                    </rule>
                  </rules>
                </rewrite>
              </system.webServer>
            </configuration>

            Caveats

            • in this test-case, I put my web.config in the directory /wp-content/uploads/sites/3/2015/11, for uploads from this month. You can put the web.config file anywhere in /wp-content/uploads/sites/5
            • I had to clear all previous set URL rewrite rules using <clear />, since they interfered with this rule
            • this is a rough example, it would be better to check a cookie and its value

            Translation of the rewrite rule to .htaccess should be possible and should still work if you use Helicon Ape and .htaccess files.

          2. There’s a thought, but unfortunately no sollution… When I use this in the web.config in the uploads folder, all images, etc. are blocked, also for the public parts. Well of course! But when I use this in a web.config in the uploads/sites/5 folder, it doesn’t block anything…
            Any thoughts on how the location of web.config can interfere with its working?

          3. Are you currently using a web.config or .htaccess in the website root? I recommend the first (web.config). The web.config rules in a sub directory inherit rules from its parent, so a web.config in /wp-content/uploads inherits the rules from a web.config in /wp-content – if one exists there – and /. Therefore I needed to clear all previous rewrite rules, see the line <clear />.

            Of course this’ll block everything when you put the file in /wp-content/uploads, I put mine in /wp-content/uploads/sites/3/2015/11 for testing purposes. In your situation, you should put the web.config in /wp-content/uploads/sites/5. You can expand the web.config rewrite rule to look only for .pdf and .docx extensions, for example add a second condition:

            <add
              input="{URL}"
              pattern="^*.pdf$"
              negate="false"
              ignoreCase="true"
            />
          4. No htaccess, web.config in the root. It looks like a default WP web.config (along with the wp-config).

            Like I said, I had your code in /uploads/sites/5 but it didn’t work. I did in /uploads/
            When I add the new code, it also blocks access to logged in users. I can manage that in many ways :-)
            I put the second add right below the previous add (about the cookies).

            It looks like I’m hijacking your thread which is basically about something else. Should we better continue by email or shall I make a ticket at Vevida?

          5. In my mulitsite WordPress environment, the given example web.config does exactly what you want it to do. If you can create a support call with our customer service, one of my colleagues or I can have a look at the web server to identify the problem.

  3. 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.)

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

    1. Hi Bob,
      Thank you for your comment, well noted! This must have been a “plugin gone bad” issue, the spaces are in my production web.config. Anyway, I’ve added the spaces in this post, thanks again!

Hi! Join the discussion, leave a reply!