Category Archives: IIS

IIS REWRITE II – CURSE OF THE 500 URL Rewrite Module Error.

A few months back I wrote about how I set up the URL rewriting for this blog.   About two weeks ago I read some chatter on Twitter about using Dareboost to optimize one’s blog.  It had several recommendations when I ran it on my site, one of them being to enable IIS compression.  After some fiddling about I got it to work on my shared hosting plan, and thought nothing else of it.

Fast forward to today and I notice one part of my site isn’t working – the date picker on my very simplistic Amortization calculator isn’t working.  Well the problem turned out to be, the rewriting rules I had set weren’t covering links to CSS and Script.  Easy enough – I’ll just quickly add an extra outbound rule to cover this.   Well, every time I added a rule it either caused the entire site or the calculator to return empty responses, which I determined were due to HTTP 500 errors.  I couldn’t understand why adding a simple new rule was causing this, even when I modified it to take no action. 

It turns out the problem was this – and it makes sense – you can’t have an outbound rule if you have compression enabled.  IIS rewrite can’t scan for links, anchors etc. in the response if the content is compressed.  But it would be great if it could somehow alert you why this is happening or perhaps inspect the site configuration and display a warning when you open the URL rewrite feature in IIS Admin.

HOSTING WORDPRESS ON IIS, YOU WON’T BELIEVE WHAT YOU DON’T KNOW [PICS] [NSFW]

Ok, so link bait headlines are the order of the day

One tricky issue I had when setting up this website is that the domain itself (knarfalingus.com) is not the main domain of the site.   This is a shared hosting account, so on disk,  what I will call the ‘primary’ domain points to the root folder ‘\’ , and ‘knarfalingus.com’ is a domain pointer that points to the same IP address.  I wanted ‘knarfalingus.com’ to point to a subfolder. The way I have it set up, this domain is hosted on disk in a subdirectory of the root, ‘\knarfalingus.com’

In order to achieve this, various rewrite rules were put in play.  I’ll just refer to the two excellent articles I used when I first set up the site for details, but they should cover most of what is needed to handle the situation.  The web.config containing these rules go in the root folder.

http://weblogs.asp.net/owscott/archive/2010/01/26/iis-url-rewrite-hosting-multiple-domains-under-one-site.aspx

http://weblogs.asp.net/owscott/archive/2010/05/26/url-rewrite-multiple-domains-under-one-site-part-ii.aspx

For the starting point of this post, this is the web.config for the case when the you have a domain pointer “yourdomain.com” and you want to host it on disk in a same named subfolder of the root i.e. ‘\yourdomain.com’

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <system.webServer>
		<rewrite>
			<rules>
        <rule name="yourdomain.com" enabled="true" stopProcessing="true">
          <match url=".*" />
          <conditions logicalGrouping="MatchAll" trackAllCaptures="false">
            <add input="{HTTP_HOST}" pattern="^(www\.)?yourdomain\.com$" />
            <add input="{PATH_INFO}" pattern="^/yourdomain\.com($|/)" negate="true" />
          </conditions>
          <action type="Rewrite" url="/yourdomain.com/{R:0}" />
        </rule>
      </rules>
			<outboundRules>
				<rule name="Outgoing - URL paths" preCondition=".aspx pages only" enabled="true">
					<match filterByTags="A" pattern="^(?:yourdomain|(.*//[_a-zA-Z0-9-\.]*)?/yourdomain\.com)(.*)" />
					<action type="Rewrite" value="{R:1}{R:2}" />
				</rule>
				<rule name="response_location URL">
					<match serverVariable="RESPONSE_LOCATION" pattern="^(?:yourdomain|(.*//[_a-zA-Z0-9-\.]*)?/yourdomain\.com)(.*)" />
					<action type="Rewrite" value="{R:1}{R:2}" />
				</rule>
				<rule name="response_location querystring">
					<match serverVariable="RESPONSE_LOCATION" pattern="(.*)%2fyourdomain\.com(.*)" />
					<action type="Rewrite" value="{R:1}{R:2}" />
				</rule>
				<preConditions>
				   <preCondition name=".aspx pages only">
					   <add input="{SCRIPT_NAME}" pattern="\.aspx$" />
				   </preCondition>
				</preConditions>
			</outboundRules>
		</rewrite>
    </system.webServer>
</configuration>

Although this was satisfactory, I recently wanted to add permalinks to my WordPress site.  But doing so resulted in nasty 404 errors in IIS.   Following the advice here would probably work on most sites but not on mine due to the special setup. 

First, I wanted to have a URL pattern that was useful in terms of SEO and also resistant to change.  For example, in the WordPress settings, one of the built-in Permalink Types is ‘Day and Name’ which looks like so:

http://www.yourdomain.com/2014/05/26/sample-post/

Where ‘sample post’ is the post slug (which was helpfully hidden for me in WordPress when editing posts, this can be made visible via Screen Options).

To me this not a very useful format.  The date is included which might be helpful on a more time sensitive blog like a news site, but to me is meaningless. Worse still the entire format is based on the premise that your title slug never changes – which you may want to change at some later date. 

If in the future you change your scheme to

http://www.yourdomain.com/sample-post/2014/05/26/

you can rest assured you have broken all existing links to your posts!

How to solve these issues and improve SEO?  For the SEO part, I’d want the title first in the URL, as it is the most specific information.  After that I’d want the category for some more keywords. Finally, add the unique post_id to the URL.  My URL scheme is therefore as follows

image

Here are the parts of the scheme (see Permalinks for syntax)

  • /blog/ – the reason for this is explained below
  • %postname% – this is next – this is the most ‘specific’ information about the post, and I put it first in the URL for that reason.
  • %category% – this is next, less specific, but useful keywords.  If you use a category hiearchy you will get multiple keywords separated by ‘/’
  • %post_id% – this is last.   It is meaningless in terms of SEO so it can be last, and as long as I commit to keeping %post_id% last in any URL scheme I come up in the future, any existing links out on other sites should continue to work (EDIT:not with WordPress permalinks but by using IIS Rewrite, see below)

To demonstrate this, examine the following links, they will all lead back to this page, due to the non-varying placement of %post_id%

http://www.knarfalingus.com/blog/hosting-wordpress-on-iis-you-wont-believe-what-you-dont-know-pics-nsfw/wordpress/80/

http://www.knarfalingus.com/blog/still/works/wordpress/80/

http://www.knarfalingus.com/blog/still/works/now/80/

And finally this one here – which won’t work with the WordPress Permalink scheme, which expects three leading directories in the path to the %post_id%. It does work now as I address the issue below when discussing the /blog/ prefix.

http://www.knarfalingus.com/blog/80/

/blog/ prefix

The reason for this is simple, as I stated earlier I have the pre-existing rules which make the domain pointer appear as a standalone domain, but this has a consequence, the rules in the web.config in the root folder actually control the rewrites.  In order to squeeze in a rule for the blog without interfering with anything else, I chose to have a rule that handles only URLs with a specific prefix, in this case ‘/blog/’ (actually it matches /blog followed by anything, /blog1/, /blogxyz/ but for my purposes, good enough). This rewrites those URLs to index.php

        <rule name="yourdomain.com wordpress" enabled="true" stopProcessing="true">
          <match url=".*" />
          <conditions logicalGrouping="MatchAll" trackAllCaptures="false">
            <add input="{HTTP_HOST}" pattern="^(www\.)?yourdomain\.com$" />
            <add input="{PATH_INFO}" matchType="Pattern" pattern="^/blog/.*$" ignoreCase="true" negate="false" />
          </conditions>
          <action type="Rewrite" url="/yourdomain.com/index.php/{R:0}" appendQueryString="false" />
        </rule>

Now above I mentioned http://www.knarfalingus.com/blog/80/ doesnt redirect to the page – but we can fix that by changing our newly added rule to:

        <rule name="yourdomain.com wordpress" enabled="true" stopProcessing="true">
          <match url=".*" />
          <conditions logicalGrouping="MatchAll" trackAllCaptures="false">
                        <add input="{HTTP_HOST}" pattern="^(www\.)?yourdomain\.com$" />
                        <add input="{PATH_INFO}" matchType="Pattern" pattern="^/blog[\w-_/\.]*/(\d+)/?$" ignoreCase="true" negate="false" />
          </conditions>
          <action type="Rewrite" url="/yourdomain.com/?p={C:1}" appendQueryString="false" />
        </rule>

This rule captures all URLs for yourdomain.com that start with /blog and end with a number. That number is rewritten to what is the default PermaLink URL in wordpress (/?p=%post_id%).

Now URL’s like this work, note the ID is at the end of the URL:

http://www.knarfalingus.com/blog/94935/this/is/a/really/4434/long/URL-dont-you-think.aspx/342534235/test/tes/90/yada-yada-yada/the_answer_is_42/why/are/you/still/reading/this/test/test/80/

I also added a trailing ? to the RegEx to make the trailing slash optional

http://www.knarfalingus.com/blog/94935/this/is/a/really/4434/long/URL-dont-you-think.aspx/342534235/test/tes/90/yada-yada-yada/the_answer_is_42/why/are/you/still/reading/this/test/test/80

Wrapping up, many WordPress themes have a 404 page,  But I did not see it, I saw an IIS 404 page. The issue is IIS is handling the 404, instead we need to let it flow through to PHP/Wordpress. The solution is to add the following to web.config

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <system.webServer>
    <httpErrors existingResponse="PassThrough" />
    .....

Tossing in a little security, we can block others from loading our content in frames (I know, not very common anymore), by using the X-Frame-Options header and specifying SAMEORIGIN.


<?xml version="1.0" encoding="UTF-8"?>
<configuration>
	<system.webServer>
		<httpProtocol>
			<customHeaders>
				<add name="X-Frame-Options" value="sameorigin" />
			</customHeaders>
		</httpProtocol>

The final config is now as follows:


<?xml version="1.0" encoding="UTF-8"?>
<configuration>
	<system.webServer>
		<httpProtocol>
			<customHeaders>
				<add name="X-Frame-Options" value="sameorigin" />
			</customHeaders>
		</httpProtocol>
		<httpErrors existingResponse="PassThrough" />		
		<rewrite>
			<rules>
        <rule name="yourdomain.com wordpress" enabled="true" stopProcessing="true">
          <match url=".*" />
          <conditions logicalGrouping="MatchAll" trackAllCaptures="false">
                        <add input="{HTTP_HOST}" pattern="^(www\.)?yourdomain\.com$" />
                        <add input="{PATH_INFO}" matchType="Pattern" pattern="^/blog[\w-_/\.]*/(\d+)/?$" ignoreCase="true" negate="false" />
          </conditions>
          <action type="Rewrite" url="/yourdomain.com/?p={C:1}" appendQueryString="false" />
        </rule>
        <rule name="yourdomain.com" enabled="true" stopProcessing="true">
          <match url=".*" />
          <conditions logicalGrouping="MatchAll" trackAllCaptures="false">
                        <add input="{HTTP_HOST}" pattern="^(www\.)?yourdomain\.com$" />
                        <add input="{PATH_INFO}" pattern="^/yourdomain\.com($|/)" negate="true" />
          </conditions>
          <action type="Rewrite" url="/yourdomain.com/{R:0}" />
        </rule>
      </rules>
			<outboundRules>
				<rule name="Outgoing - URL paths" preCondition=".aspx pages only" enabled="true">
					<match filterByTags="A" pattern="^(?:yourdomain|(.*//[_a-zA-Z0-9-\.]*)?/yourdomain\.com)(.*)" />
					<action type="Rewrite" value="{R:1}{R:2}" />
				</rule>
				<rule name="response_location URL">
					<match serverVariable="RESPONSE_LOCATION" pattern="^(?:yourdomain|(.*//[_a-zA-Z0-9-\.]*)?/yourdomain\.com)(.*)" />
					<action type="Rewrite" value="{R:1}{R:2}" />
				</rule>
				<rule name="response_location querystring">
					<match serverVariable="RESPONSE_LOCATION" pattern="(.*)%2fyourdomain\.com(.*)" />
					<action type="Rewrite" value="{R:1}{R:2}" />
				</rule>
				<preConditions>
				   <preCondition name=".aspx pages only">
					   <add input="{SCRIPT_NAME}" pattern="\.aspx$" />
				   </preCondition>
				</preConditions>
			</outboundRules>
		</rewrite>
    </system.webServer>
</configuration>

UPDATE : after posting I noticed the categories, date, comments, feed, author links (side menu) and pages didn’t work. This seemed to be the combination of PermaLinks plus the rules I started with, not the recent rule additions. I basically exclude rewriting any subdirectories and any .php files to index.php – this seems to be working. All the permalinks seem to be working as well without any logic for the /blog prefix.

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <!-- {EA385C80-7BD5-46DC-8E47-F992B0514618}  -->
  <system.webServer>
    <httpProtocol>
      <customHeaders>
        <add name="X-Frame-Options" value="sameorigin" />
      </customHeaders>
    </httpProtocol>
    <httpErrors existingResponse="PassThrough" />
    <rewrite>
      <rules>
              <!-- permalink = /blog/%postname%/%category%/%post_id%/ -->
                <clear />
                <rule name="wordpress grab trailing post_id" enabled="true" stopProcessing="true">
                  <match url=".*" />
                  <conditions logicalGrouping="MatchAll" trackAllCaptures="false">
                    <add input="{HTTP_HOST}" pattern="^(www\.)?yourdomain\.com$" />
                    <add input="{PATH_INFO}" matchType="Pattern" pattern="^/blog[\w-_/\.]*/(\d+)/?$" ignoreCase="true" negate="false" />
                  </conditions>
                  <action type="Rewrite" url="/yourdomain.com/?p={C:1}" appendQueryString="false"/>
                </rule>
                <rule name="wordpresss urls" enabled="true" stopProcessing="false">
                  <match url=".*" />
                  <conditions logicalGrouping="MatchAll" trackAllCaptures="false">
                    <add input="{HTTP_HOST}" pattern="^(www\.)?yourdomain\.com$" />
                    <add input="{PATH_INFO}" pattern="^/wp-includes/.*$" negate="true" />
                    <add input="{PATH_INFO}" pattern="^/wp-content/.*$" negate="true" />
                    <add input="{PATH_INFO}" pattern="^/wp-admin/.*$" negate="true" />
                    <add input="{REQUEST_FILENAME}" pattern="^.*\.php$" negate="true" />
                  </conditions>
                  <action type="Rewrite" url="/index.php/{R:0}"   />
                </rule>
                <rule name="yourdomain.com" enabled="true" stopProcessing="false">
                    <match url=".*" />
                    <conditions logicalGrouping="MatchAll" trackAllCaptures="false">
                        <add input="{HTTP_HOST}" pattern="^(www\.)?yourdomain\.com$" />
                        <add input="{PATH_INFO}" pattern="^/yourdomain\.com($|/)" negate="true" />
                    </conditions>
                    <action type="Rewrite" url="/yourdomain.com/{R:0}" />
                </rule>
      </rules>
      <outboundRules>
        <rule name="Outgoing - URL paths" preCondition=".aspx pages only" enabled="true">
          <match filterByTags="A" pattern="^(?:yourdomain|(.*//[_a-zA-Z0-9-\.]*)?/yourdomain\.com)(.*)" />
          <action type="Rewrite" value="{R:1}{R:2}" />
        </rule>
        <rule name="response_location URL">
          <match serverVariable="RESPONSE_LOCATION" pattern="^(?:yourdomain|(.*//[_a-zA-Z0-9-\.]*)?/yourdomain\.com)(.*)" />
          <action type="Rewrite" value="{R:1}{R:2}" />
        </rule>
        <rule name="response_location querystring">
          <match serverVariable="RESPONSE_LOCATION" pattern="(.*)%2fyourdomain\.com(.*)" />
          <action type="Rewrite" value="{R:1}{R:2}" />
        </rule>
        <preConditions>
          <preCondition name=".aspx pages only">
            <add input="{SCRIPT_NAME}" pattern="\.aspx$" />
          </preCondition>
        </preConditions>
      </outboundRules>
    </rewrite>
  </system.webServer>
</configuration>