Skip to main content
Ben Nadel
It's not enough; but, not enough is better than nothing.
Ben Nadel at CFUNITED 2010 (Landsdown, VA) with: Alexander Friess and Gary Gilbert
Ben Nadel at CFUNITED 2010 (Landsdown, VA) with: Alexander Friess@AlexanderFriess ) and Gary Gilbert@garyrgilbert )

Exploring IIS Mod-Rewrite For Rewriting URLs In A ColdFusion Application

By Ben Nadel on

I've always liked the concept of URL rewriting; but, I've never actually known very much about it. To date, all of the URL "rewriting" that I've done has always been built on top of IIS's 404 error URLs or ColdFusion's OnMissingTemplate() event handler. This has worked, but has always been a hacky solution. Using 404 error handling is easy, but has serious cross-server setup and compatibility issues (not to mention a general level of discomfort in misusing the intent of a 404 error). OnMissingTemplate() is great for ColdFusion requests, but fails completely when you have to worry about directory-level navigation (a "must" for any usable website).

With recent talks in the office about URL rewriting, I decided it was time to learn more about it. To start out with, I googled, "IIS URL Rewriting," and started to look at the options that were available. After reviewing a few products, I settled on IIS Mod-Rewrite by MicroNovae; it marketed itself as the most Apache-Mod-Rewrite-compatible of all URL rewriting plugins for IIS and I figured that was a good sign. Plus, it had the ability to allow htaccess overwriting of rewrite rules which meant that I could easily create application-specific or even directory-specific rules without having to mess with a centralized configuration file.

Once I installed IIS Mod-Rewrite on the local server, I went into the configuration and added my application directory (a sub-directory of my R&D folder) as a rewrite-enabled path. This created an ".htaccess" file in my testing directory where all of my rewrite rules would be used. As it turns out though, some existing functionality on the local server was using that file extension (htaccess) for password protection, so I had to change my rewrite config file extension to ".seo", a setting available in the IIS Mod-Rewrite configuration UI.

With that set up, I was ready to start rewriting URLs. Now, when it comes to rewriting URLs, I still want the bulk of my logic to be located within my ColdFusion application. I understand that there are efficiencies involved in doing things at the IIS level; but, I don't want to start distributing the business logic of my application too widely. As such, I wanted IIS Mod-Rewrite to handle URL rewriting in the smallest way possible. To accomplish this, I set up only a single rule in my application-config file: If the requested file doesn't exist, rewrite the URL as pointing to the application's front-controller (index.cfm):

# IIS Mod-Rewrite configuration file
# Turn on the rewrite rules for this access file.
# This will will handle all requests based off of
# this directory.

RewriteEngine On

# Check to see if the requested file eixsts. If it does not,
# then we are going to redirect to the index (front-controller)
# file of this directory (the root of our application).

RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.+)$ index.cfm?\$404=$1 [L,QSA]

Our first two rewrite conditions (RewriteCond) simply check to see if the requested does not match an actual file or a directory. In this way, I will only intercept requests to files that don't truly exist within this specific application. The last line there is the actual rewrite rule for the requested URL. The requested URL is relative to the location of my application-specific configuration file. So, for example, if I asked the browser for the URL:


... and my ".seo" config file was located in the directory:


... then the url I would capture in the rewrite is simply:


Notice that this URL is not a full, web-root-relevant script name; rather, it is only the requested file as relative to the local htaccess (.seo) file. While this complicates some things, this will actually make our URL pattern matching extremely easy as we don't have to worry about our meta-request-environment noise (ex. server name and parent directories).

When rewriting the URL, I capture the application-relevant file request and add it to the new query string using the key "$404". This way, I can easily see what the original file request was when I get to the ColdFusion level. By default, IIS Mod-Rewrite stores the full, original request in the HTTP headers at "X-Original-URL" (available within the config file as %{HTTP_X-Original-URL}), but this includes the full file path and query string, which we would have to parse manually. By adding the captured file to the rewritten query string, I know that I am getting the target file and only the target file.

At the end of the rewrite rule, I am using the flag, QSA. This appends the original query string to the rewritten query string such than all URL variables get passed on to the rewritten page request. This allows our ColdFusion application to use all the "expected" URL variables plus the $404 one that I created as part of the URL rewrite.

But enough about the rewriting layer - like I said before, I only want IIS Mod-Rewrite to touch the request in the most minimal way; I want the rest of the processing to be handled at the ColdFusion level, where I and all of my developers will be comfortable. So let's look at some ColdFusion code already!

Because IIS Mod-Rewrite re-routes invalid file and directory requests to our ColdFusion application front-controller, we have to check for the existence of the $404 URL key to see if the current request is using SES/SEO urls. If the $404 key doesn't exist, the request is a standard ColdFusion request; if the $404 key does exist, the current ColdFusion request has been rewritten. Because each of these cases is seen by ColdFusion as a standard request, I have to do some URL checking in the OnRequestStart() event handler in the Application.cfc in order to properly prepare request-based variables:


	hint="I define the application settings and event handlers.">

	<!--- Define the application. --->
	<cfset = hash( getCurrentTemplatePath() ) />
	<cfset this.applicationTimeout = createTimeSpan( 0, 0, 5, 0 ) />

	<!--- Define page request settings. --->

		hint="I intialize the page request.">

		<!--- Define arguments. --->
			hint="I am the requested script name."

		<!--- Define the local scope. --->
		<cfset local = {} />

			Combine the form and url scopes into a common request
			attributes collection so that we don't have to know
			what scope a variable came from.
		<cfset request.attributes = duplicate( url ) />
		<cfset structAppend( request.attributes, form ) />

			Param the default action variable - this will be
			what the front-controller (and sub-controllers) use
			to figure out what scripts to execute.

			Split the request variable into an array such that
			we can examine the parts of it in front-controller
			control flow. We are going to assume that the raw
			action variable is a dot-delimmited list of actions.
		<cfset = listToArray(,
			) />

			Copy the script name to the request scope. We are
			going this because the script name is used in relative
			web-root calculations and might need to be overriden
			later on (and we cannot override CGI values).
		<cfset request.scriptName = cgi.script_name />

			Now that we have our action variable set up and based
			of the query string, let's check to see if the current
			page request is actually a URL Rewriting. If so, we
			might have to translate the URL into a new action AND
			a set of query string parameters.

			NOTE: Our IIS MOD-Rewrite interceptor rewrite rule
			will create a "$404" entry in our URL scope if the
			requested URL did not truly exist.
		<cfif structKeyExists( url, "$404" )>

				Because this application has its own local rewrite
				access file, the "404" script passed in by the
				rewrite rule is local to the current application
				directory and NOT to the web root. Example:


				Notice that it is not a true script name. This is
				nice for a pattern-testing, but not so nice if we
				want the request of our request to act uniformly
				towards the script URL. As such, let's take the
				404 script and convert to a full script.

				Since our rewrite rule always passes back to our
				index.cfm front-controller, we know that the CGI-
				based script name is always in the same
				directory as our Application.cfc file. As such,
				we can use that script to build our 404-relative
			<cfset request.scriptName = (
				getDirectoryFromPath( cgi.script_name ) &
				url[ "$404" ]
				) />

				Now that we have our script name calculated, let's
				use the 404 script, which is current-directory-
				relative, to figure out if we need to update our
				action variable at all.
			<cfif reFind( "^contact\b", url[ "$404" ] )>

				<!--- Routing to contact section. --->
				<cfset = [ "contact" ] />

			<cfelseif reFind( "^about\b", url[ "$404" ] )>

				<!--- Routing to about section. --->
				<cfset = [ "about" ] />

			<cfelseif reFind( "^blog/[\d+]", url[ "$404" ] )>

				<!--- Routing to blog section. --->
				<cfset = [ "blog" ] />

				<!--- Get ID of blog post. --->
				<cfset = listGetAt( url[ "$404" ], 2, "/" ) />

			<cfelseif reFind( "^blog\b", url[ "$404" ] )>

				<!--- Routing to blog section. --->
				<cfset = [ "blog" ] />


					We could not match the requested URL against
					any of our SES patterns. As such, this is
					truly an invalid file request. As such, let's
					return a true 404 error.
					statustext="Page Not Found"

					Return out with false so the request of the
					page will not get processed.
				<cfreturn false />


				Now that we are done with our IIS Mod-Rewriting,
				let's get rid of the URL and request variables
				that we don't need.
			<cfset structDelete( url, "$404" ) />
			<cfset structDelete( request.attributes, "$404" ) />


			Get the relative web root path from our current page
			(this will allow our traversal path to always be
			relative, rather than a hard-coded root path, which
			is the lame). Because our script name may appear
			DIFFERENT to the browser than it does to the ColdFusion
			server if any rewriting took place, we have to get the
			web root based on the normalized script name (the one
			stored in the request).

			NOTE: When comparing the script-path and the
			base-path, I am adding junk at the end to make sure
			we don't miscalculate due to a directory request and
			not a file request (ColdFusion will ignore the last,
			empty list item).
		<cfset request.webRoot = repeatString(
				listLen( expandPath( request.scriptName & "file" ), "\\/" ) -
				listLen( getCurrentTemplatePath(), "\\/" )
			)) />

		<!--- Return true so that the page can be processed. --->
		<cfreturn true />

		hint="I execute the page request.">

		<!--- Define arguments. --->
			hint="I am the requested script name."

			No matter what page was requested, let's include
			index.cfm (our front-controller for this application).
		<cfinclude template="./index.cfm" />

		<!--- Return out. --->
		<cfreturn />


As you can see in the Application.cfc above, this mini-app uses an action variable (Request.Do) to route page requests through the front-controller. For a standard page request, this action is pulled out of the URL/FORM collection; however, if the URL was rewritten, as denoted by the existence of the $404 URL variable, the original URL is matched against several regular expressions. If one of the regular expression patterns matches the original URL, the action variable and other URL values are overridden based on the given business logic.

Once all of the URL rewriting is dealt with, one of the last things that we need to do in the OnRequestStart() event handler is to calculate the relative web-root of the original URL. This is a minor but extremely important point. Remember, URL rewriting happens on the web server; as such, from the client's (browser) perspective, the URL has not changed at all. So while we may be in the application root of the server, the client might think that we are 4 directories deep according to its URL. To make sure that things don't break in the rendered page, all of our linked items (anchor tags, images, script files, CSS files, etc.) must use a web-relative root of "../../../../" to go along with the original URL.

After that relatively minor variable logic, the ColdFusion request can proceed as normal. To test that things are all working nicely, I created a simple front-controller (index.cfm) that outputs request information:



	<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "">
		<title>IIS Mod-Rewrite ColdFusion Demo Application</title>

			IIS Mod-Rewrite ColdFusion Demo Application

			Test Links

			<a href="#request.webRoot#contact/"
				>#request.webRoot#contact/</a><br />

			<a href="#request.webRoot#about/"
				>#request.webRoot#about/</a><br />

			<a href="#request.webRoot#blog/"
				>#request.webRoot#blog/</a><br />

			<a href="#request.webRoot#blog/1234/"
				>#request.webRoot#blog/1234/</a><br />

			Relative Web Root

			<cfif len( request.webRoot )>
				#request.webRoot#<br />
				[empty string]

			URL Scope


			REQUEST Scope




Notice that the links I am providing are all built using the relative web-root calculated in the OnRequestStart() event handler. This will prevent them from breaking, regardless of what URL I try to access. To demonstrate that we can use the requested URL to create both action variables as well as URL variables, I am going to go to this URL:


When I hit that page (which doesn't exist), the URL is rewritten, my front-controller gets executed, and the following page is rendered:

IIS Mod-Rewrite Can Be Bulit To Rewrite ColdFusion Application URLs.

Notice that the URL "/blog/1234/" was both converted into the action variable [ "blog" ] as well as the URL variable, "1234." Notice also that the relative webroot was appropriately calculated to be "../../" for the two directories down from our application root that our URL appears to be from the client-perspective.

My first foray into URL rewriting has been quite enjoyable. IIS Mod-Rewrite seems like a very powerful and easily configurable tool. What I especially like about it is that I can distribute my application-specific rewriting to individual configuration files of my own naming. I also like that while I can use IIS Mod-Rewrite to handle URL rewriting, I can have it set up so that the bulk of my business logic still lives in the application itself. Not only do I feel that this is a more natural solution, I feel that it will be easier to use and update going forward.

Reader Comments

Hey Ben, yeah I got pretty excited once I got into Apache mod_rewrite and did some cool stuff. It's powerful stuff!

I've read that .htaccess files should only be used if you're unable to use a more global (or per-virtual host) configuration, because it makes for a much greater performance hit. Every site that allows for .htaccess files requires file system checks for each relevant directory in a request. This may be microseconds, but worth noting. That said, it seems just about everyone uses them :)

FWIW, I don't think the [L,QSA] options in your example are required. If it's the last rule anyway, no need to explicitly say "L" (last rule); and I'm pretty sure QSA is "on" by default -- at least it is in Apache mod_rewrite.

Happy URL rewriting ;-)

Ooh, and I just remembered a cool trick that Barney Boisvert posted a little while back (mod_rewrite to "cache" CF-generated images):


As long as you are using IIS7+, I would agree with iamhrh that IIS' own URL-rewriting module is superb and integrates very nicely - and can even be designed via the IIS Manager interface.

Unfortunately, Apache's own url_rewrite module is very hard to use. Because of the way Apache's module works and because of the level at which it hooks into Apache's request processing pipeline, I have not been able to get it to work for me. There are various mod_rewrite lookalike mods for IIS that work differently and hook in differently from Apache's mod_rewrite, so that tends to make them more usable.


I've used Helicon's ISAPI Rewrite on IIS6 in a similar fashion for a couple of years now. Recently I've been turned on to their new product Ape, which provides an extended URL rewriting experience in addition to a full suite of Apache emulation functionality. There's a slew of apps like you've mentioned available, however I find this is perhaps the most comprehensive solution for the developer that loves Apache but is working with a client or employer that's deadset on MS server environments.

@Iamhrh, @Justice,

I read a bit into the IIS7 stuff, but my machines are all on IIS6, so I had to experiment with the plugin / interceptor route. But yeah, I was always assume that whatever is native will be better / faster / more efficient than anything one could install on top of it. Perhaps one day :)


I can see the individual config files making a bit of a hit, but right now, I feel that they have such great benefits - the file path being relative to them a HUGE one in terms of pattern matching on the ColdFusion side.

As far as the "L", you are correct; but, from my testing, it seems that "QSA" is not on by default - you have to add it to get the URL variables to cascade down through the rewrites.


Ok sounds cool - I will take a look at it. Right now, I'm on the demo version of IIS Mod-Rewrite, which last for 30 minutes before IIS needs to be restarted; as such, I'm not committed to anything at the moment.

I'm wondering if using "$404" as a URL key is a good idea. I worry that search engines might consider a url containing "404" as a broken link. I'm not sure, but you should look into it.

Why use "$404" as the URL key anyways? Why not "$rewrite=" or something? "404" is kind of a misnomer in this case.

Good in-depth post though, wish it would've existed before I had to learn it on my own.

BTW I use Ionics Isapi Rewrite Filter:

Great right up on getting into URL re-writing. We have Helicon's ISAPI Rewrite installed at work on the Win 2003 ColdFusion servers and allow customers on individual websites the ability to do their own re-writing using their .htacess file. We've had this for over 3 months now and I don't remember hearing of any performance concerns doing it this way.


The search engines don't actually see the "$404" query string. The URL rewriting happens on the server and is transparent to the client.


Good to know that you have not seen any performance issue with htaccess files. Even if there was a slight hit, I'd say it's probably worth the ease of organization that htaccess files provide.

@Justice, @Giancarlo,

The conversations that we've been having in the office lately (that led me to look into this stuff) were actually prompted by someone who does use that as well (Ionic). He swears by it. But since I was very new, I figured I would turn to Google, just to see what's out there.


The easiest thing for me was to use IIS Rewrite addin (or IIRF in IIS5-6) with the following rules:

Static/Media Files:
for all paths looking like '^/media/.*$', don't do anything and just return the file as usual; otherwise...

Dynmic Pages:
for all paths ('^.*$'), simply take that path, prepend it with '/index.cfm' (i.e. '/sub/folder/page' => '/index.cfm/sub/folder/page').

Then in ColdFusion, I simply use onRequest (or onMissingTemplate if I choose not to have an index.cfm file), inspect the CGI.PATH_INFO ('/sub/folder/page' in this case), and figure out where to go from there.

URL (querystring) parameters are not affected in any way - they are just copied from the original URL to the rewritten URL, and are available in onRequest and anything that onRequest calls.

But the web server is not adding new URL parameters and potentially creating conflicts. The web server is only taking what used to be '/sub/folder/page' and rewriting it to '/index.cfm/sub/folder/page', so it is setting the new CGI.PATH_INFO to the old CGI.SCRIPT_NAME, '/sub/folder/page', and then changing the new CGI.SCRIPT_NAME to be '/index.cfm'.

If you look at the HTTP/CGI-variables standards, you will find that CGI.SCRIPT_NAME is the part of the path that corresponds to a real file on the hard drive, while CGI.PATH_INFO is the part of the path appended to CGI.SCRIPT_NAME, and that FULL_PATH = "#SCRIPT_NAME##PATH_INFO#". (However, typically a web server will set PATH_INFO = SCRIPT_NAME if PATH_INFO would otherwise be blank.)


@Ben: My bad on the QSA (query string append) flag -- you're absolutely correct. It's required in your example and would be with Apache mod_rewrite as well. I should have confirmed with the docs before commenting ;-)

My confusion stemmed from the fact that the query string *is* automatically appended by default when you don't explicitly define a query string in your rewrite target. However, the default is to drop it when you explicitly add a query string, hence the need for QSA in your example.

I've always loved the concept of URL Rewriting. On Apache it's really quite simple. I've always found the solutions on IIS horribly frustrating. Your POST made me take a another look at ISAPI_rewrite which I already had installed on one of my boxes. I've upgraded to the latest version ( and I can't get any of of the examples including the one above to work! Does any one have any recommendations?


I'm curious as to how you find Apache's mod_rewrite simple and IIS's rewrite tools (IIS URL Rewrite? IIRF?) frustrating.


I'm curious as to how you find Apache's mod_rewrite simple and IIS's rewrite tools (IIS URL Rewrite? IIRF?) frustrating.


I'm curious as to how you find Apache's mod_rewrite simple and IIS's rewrite tools (IIS URL Rewrite? IIRF?) frustrating.

Apache's mod_rewrite hooks into the pipeline step where Apache translates from a URL path into a filesystem filename.

That is not at all what I want from a URL rewriting module. Even the Apache documentation describes its approach as a hack and the only way of implementing URL rewriting in Apache. The documentation cautions the uninitiated that the art of Apache mod_rewrite tends towards magic and the black arts.

So far, I have not been able to get Apache mod_rewrite working in the way I want it to work (translate /a/b/c.htm to /index.cfm/a/b/c.htm, in a .htaccess file, all within in a virtual path).

What I want is a pipeline step where the URL requested is translated into a new request URL. Only then should the pipeline step of translating from URL to filename occur. I don't want the two steps merged together, and I don't want to hook into the second step. I want to do URL-rewrites by simply hooking into the first step.

This is the way the IIS solutions that I have seen tend to work (IIS addin, IIRF). Both of these solutions are free of charge (one is free-open-source). I have gotten both of these solutions to work in the same way specified above. Neither of these solutions describes itself as a hack made necessary by the web server architecture.

I am not actually trying to advocate for IIS here. But I am cautioning against Apache mod_rewrite with ColdFusion, in an agile, install-anywhere, xcopy system. I am cautioning against only this module for only this web server, and not for any other modules or for any other web servers.



It's funny you mention that 'cause I was thinking about actually exploring that very concept (appending index.cfm) at lunch time.


I've only spent 15 minutes on it today but I spent hours way back when I originally got it. It's not so much the rules but the packages. Please keep in mind that my experience with the solutions on IIS are a bit dated. It's been years since I've tried the others. I chose ISAPI_Rewrite at the time because it was the only one saw some people using with coldfusion. My previous attempt to get this working resulted in rewrite rules that work with static file or ASP but NOT CF. Now nothing works. Do you have experience with ISAPI_Rewrite?


When I've used rewriting in Apache I wouldn't say I was a magician but I was actually able to get somewhere with it. It's partially due to lack of attention and urgency but my attempts with ISAPI_rewrite and CF have not been fruitful.

When using rewriting with Apache and PHP I've found using a single PHP file to handle everything the best approach. The rules break the URL down but the PHP file decides what to do with the request. It can serve static content, include dynamic content or return a location header. It's not the best solution but it worked well. I work almost exclusively with coldfusion and would like to find a similar solution.


I can imagine that in the past, url-rewriting with IIS would be all but impossible. But so was url-rewriting with Apache, unless all you needed were a few extraordinarily simplistic rules. I assume that that is at least part of the reason various packages come with their own url packages (Django, Rails).

You may want to take another look at the current state of url-rewriting in IIS. With the native url-rewriting module, or with IIRF, url-rewriting just works the way I would expect it to. And, for me, they have integrated very well with Adobe CF 8-9.

I don't have any experience with ISAPI_Rewrite, unfortunately - but I can tell you that IIRF and IIS's native addin both work very well for me, in the way I would expect url-rewriting to work, and that I have been able to construct a routing scheme a la Rails using both of these rewrite engines.



Thanks I will have a look around. Is the native stuff you're talking about specific to IIS7? Does IIRF support per-site/virtual host or per-directory configs?


Microsoft provides its own IIS7+ url-rewriting module. It's very nice, integrates directly into IIS7/ASP.NET integrated request-processing pipeline, and has very nice configuration.

IIRF is a free-open-source and works very well for me. It supports per-virtual-directory configuration very easily, which in my opinion is a firm requirement. I have not checked per-site.



I agree with the per-directory requirement; that's what I liked about IIS Mod-Rewrite - it allows per-directory config files (virtual or not). I can't imagine trying to move all site rules through a single file :)

for IIRF I used
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.+)$ /home [L,QSA]

it was not working


I'm sorry, I don't use IIRF, I cannot be sure what is wrong. Have you tried turning on logging to see what the processing flow is doing?

Has anyone been able to get outbound url rewriting working with IIS 7.5 and CF9?

I keep getting a 500.52 error. I have read here:
from another person seeing the same problem. He tried to get around it by disabling Chunking transfer encoding but didnt get it working.

Its only not working with CFM pages as no cf pages work on the site with the outbound rewrite rule enabled.

I'm trying to use URLRewriter on Windows 2008 R2 with IIS 7.5 with CF9, and created a custom page to show the customized 404 message.
On the start of page i'm issuing a customized 404 header command :
<cfheader statuscode="404" statustext="lblPaginaDocNaoEncontrados">

the server is delivering the page correctly from that class, but adding at page beginning the server error (before the <HTML> tag) :
"The resource you are looking for has been removed, had its name changed, or is temporarily unavailable."

I tried to remove custom error file from IIS console but no way to remove it.

If I dont issue cfheard with 404 it doesnt appear.

I'm doing cfheader to make bots and other tools knows that resource wasnt found.

Any ideas ?