My WordPress web.config

Published on Monday, 22 June 2015

Do you host your WordPress website on Windows Server IIS? And are you 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 site. Maybe it's because I'm a WordPress on Windows Server IIS enthusiast, so here is my web.config for your convenience (really, it's not that special).


WordPress web.config for Windows Server 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.

Here is my web.config for WordPress on IIS :

<?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-blocklists/
		www.saotn.org/project-honey-pot/
		-->
		<section name="HttpBL" type="HttpBL.Settings" />
	</configSections>
	<appSettings/>

	<!--
		Configure HttpBL settings, choose what is best for your situation
	More about HttpBL: /posts/filter-web-traffic-with-blocklists/
	-->
	<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&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>
		<rules>
			
			<!--
				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
			-->
			<!-- Start WordPress Permalinks -->
			<rule name="WordPress Rule 1" stopProcessing="true">
				<match url="^index\.php$" ignoreCase="false" />
				<action type="None" />
			</rule>
			<rule name="WordPress Rule 2" stopProcessing="true">
				<match url="^wp-admin$" ignoreCase="false" />
				<action type="Redirect" url="wp-admin/" redirectType="Permanent" />
			</rule>
			<rule name="WordPress Rule 3" stopProcessing="true">
				<match url="^" ignoreCase="false" />
				<conditions logicalGrouping="MatchAny">
					<add input="{REQUEST_FILENAME}" matchType="IsFile" />
					<add input="{REQUEST_FILENAME}" matchType="IsDirectory" />
				</conditions>
				<action type="None" />
			</rule>
			<rule name="WordPress Rule 4" stopProcessing="true">
				<match url="^([_0-9a-zA-Z-]+/)?(wp-(content|admin|includes).*)" ignoreCase="false" />
				<action type="Rewrite" url="{R:2}" />
			</rule>
			<rule name="WordPress Rule 5" stopProcessing="true">
				<match url="^([_0-9a-zA-Z-]+/)?([_0-9a-zA-Z-]+/)?(.*\.php)$" ignoreCase="false" />
				<action type="Rewrite" url="{R:3}" />
			</rule>
			<rule name="WordPress Rule 6" stopProcessing="true">
				<match url="." ignoreCase="false" />
				<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" />
			<!-- block some more
				<add ipAddress="..." allowed="false" />
				<add ipAddress="..." allowed="false" />
				<add ipAddress="..." 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="/&" />
				<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&task=plugin&plugin=imgmanager&file=imgmanager&version=1576&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&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>
					</filteringRule>
				</filteringRules>
			</requestFiltering>
		</security>
	</system.webServer>

	<!--
		WordPress wp-login.php security: IP address AllowList,
		all IP addresses not listed below are denied access to /wp-login.php.
		IIS IP and Domain Restrictions - or IP Security - module

		@ /posts/filter-web-traffic-with-blocklists/
		@ /posts/iis-10-ftp-ip-security-allowlist/
	-->
	<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
	@ /posts/disallow-direct-access-to-php-files-in-wp-content-uploads/
	-->
	<location path="wp-content/uploads">
		<system.webServer>
			<handlers accessPolicy="Read"/>
		</system.webServer>
	</location>
</configuration>

do you want to learn about more security measurements you can take to secure WordPress on Windows Server? Have a look at: HackRepair.com's Bad Bots .htaccess translated to web.config, for IIS. Lear to enable HSTS Strict-Transport-Security header in IIS, how to remove IIS Server header or add SSL in WordPress.

Update: renamed "whitelist" to "allow list".