My WordPress web.config

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: https://www.saotn.org/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

		@ https://www.saotn.org/filter-web-traffic-with-blocklists/
		@ https://www.saotn.org/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
	@ https://www.saotn.org/disallow-direct-access-to-php-files-in-wp-content-uploads/
	-->
	<location path="wp-content/uploads">
		<system.webServer>
			<handlers accessPolicy="Read"/>
		</system.webServer>
	</location>
</configuration>Code language: HTML, XML (xml)

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”.

Jan Reilink

Hi, my name is Jan. I am not a hacker, coder, developer or guru. I am merely an application manager / systems administrator, doing my daily thing at Embrace - The Human Cloud. In the past I worked for clidn and Vevida. With over 20 years of experience, my specialties include Windows Server, IIS, Linux (CentOS, Debian), security, PHP, websites & optimization. I blog at https://www.saotn.org.

21 Comments
Newest
Oldest Most Voted
Inline Feedbacks
View all comments
16/12/2020 16:25

HI thanks a lot!!!

Can you send me or publish the folders and files permissions you use on IIS for WordPress

Again thanks for sharing!!!

Mehran
12/02/2020 16:03

Hello
I tried your setting in my IIS 10 but i got several error. Is it compatible with IIS 10 ?

19/05/2017 17:46

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 …

08/05/2017 22:48

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.

Bob
19/05/2016 18:36

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

Roy
23/11/2015 10:35

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

Roy
30/10/2015 17:05

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.

Roy
Reply to  Jan Reilink
31/10/2015 09:32

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 :-)

Roy
Reply to  Roy
02/11/2015 15:14

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.

Roy
Reply to  Jan Reilink
10/11/2015 11:07

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?

Roy
Reply to  Jan Reilink
10/11/2015 14:27

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?

03/07/2015 16:01

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"