Rick Strahl's Weblog  

Wind, waves, code and everything in between...
.NET • C# • Markdown • WPF • All Things Web
Contact   •   Articles   •   Products   •   Support   •   Advertise
Sponsored by:
Markdown Monster - The Markdown Editor for Windows

IIS 7 Error Pages taking over 500 Errors


:P
On this page:
continued from Page 1

Recently I’ve run into a problem on my production server where results that produce errors that are handled by the application (either in Application_Error or in some cases for JSON or XML based custom services) would return IIS error pages instead of my custom error content generated in code. Basically what’s happening in all of this handled error code is that the application somehow intercepts an error – either Application_Error, or in the case of a JSON service an exception in the RPC call for example – and an error handler kicks in to handle the problem. The error handler then sets the status code to 500 and generates some output that is appropriate. For Application_Error a custom HTML page is generated . For the JSON service a custom JSON error object is returned.

For example the following is the error output routine in my custom JSON Request processor:

/// <summary>
/// Returns an error response to the client from a callback. Code
/// should exit after this call.
/// </summary>
/// <param name="ErrorMessage"></param>
public void WriteErrorResponse(string errorMessage, string stackTrace)
{
    CallbackException Error = new CallbackException();
    Error.message = errorMessage;
    Error.isCallbackError = true;
    Error.stackTrace = stackTrace;

    JSONSerializer Serializer = new JSONSerializer();
    string Result = Serializer.Serialize(Error);


    HttpResponse Response = HttpContext.Current.Response;
    Response.ContentType = ControlResources.STR_JsonContentType;

    Response.StatusCode = 500;
    Response.Write(Result);
    HttpContext.Current.ApplicationInstance.CompleteRequest();
    //Response.End();
}

The status code is set to 500, and in this case a JSON error object is written to the response. The idea is that the request should generate a 500 error, but still provide appropriately formatted error information – in this case JSON – to the client. This works fine on my development machine on Vista with IIS 7, but fails in the live environment on IIS 7 Windows Server 2008.

On the live server I the response that the server generates is not my JSON object, but rather than HTML error page that IIS generates which results in an error without an information of what went wrong. So instead of a message that lets the user know that something went wrong (like Login first please) the result comes back with a non-descript error message:

JsonError

rather than the more descriptive error message that should be returned. Not cool!

The above is maybe a little bit specialized of an example since it involves a custom handler and a JSON HTTP response returned. But this issue really affects any server response that needs to return a controlled 500 error. Another example, and that’s likely more inline what most of you are doing: Trying to generate custom dynamic Application_Error messages that display error messages specific to the application and include specific application related information in it that static error pages can handle.

I tend to have fairly elaborate Application_Error handlers that allow me to switch error display modes easily for example, and allow users to create custom error pages through the admin interface. Here’s a small snippet of the handler that returns a generic error message from the application.

When an error occurs in the application Application_Error fires and eventually this code block is fired:

else if (App.Configuration.DebugMode == DebugModes.ApplicationErrorMessage)
{                    
    string stockMessage = App.Configuration.ApplicationErrorMessage;

    // Handle some stock errors that may require special error pages
    HttpException httpException = serverException as HttpException;
    if (httpException != null)
    {
        int HttpCode = httpException.GetHttpCode();
        Server.ClearError();

        if (HttpCode == 404) // Page Not Found 
        {
            Response.StatusCode = 404;
            MessageDisplay.DisplayMessage("Page not found",
                "You've accessed an invalid page on this Web server. " +
                stockMessage);
            return;
        }
        if (HttpCode == 401) // Access Denied 
        {
            Response.StatusCode = 401;
            MessageDisplay.DisplayMessage("Access Denied",
                "You've accessed a resource that requires a valid login. " +
                stockMessage);
            return;
        }
    }

    // Display a generic error message
    Server.ClearError();
    Response.StatusCode = 500;
    
    MessageDisplay.DisplayMessage("Application Error",
        "We're sorry, but an unhandled error occurred on the server. " +
        stockMessage);

    return;
}

return;

that is supposed to return a custom error page. However,  I still get the IIS 500 error page instead:

ServerError500

What’s confusing about this is that the above error is an IIS error not an ASP.NET error. The real confusion here occurs because the error is trapped by ASP.NET, but then ultimately still handled by IIS which looks at the 500 status code and returns the stock IIS error page. There are two layers of indirection at work here – the ASP.NET customErrors settings and the IIS error page handlers.

Specifically this relates to the Error Pages server setting in the IIS admin interface:

 ErrorPagesIIS[4]

which effectively tells IIS how to display errors. What’s not so obvious here is that this setting overrides the local Web.config setting of <customErrors> – if a 500 error is returned the above error kicks in but only if customErrors is kicking in (On or RemoteOnly). This behavior is new to IIS 7 I believe, maybe even new to IIS 7 SP1 and related to the excellent new server information that is displayed on these error pages when running locally. The behavior I describe realistically applies only to Integrated Mode – the classic ISAPI mode still handles errors the way they always were handled.

What really bites about this is that it’s hard to catch this particular issue. In development you typically never actually see this because you’re likely to be running with RemoteOnly for <customErrors<, so at development the behavior is actually just peachy – you see your code generated errors just fine. The error pages are ignored and ASP.NET does as ASP.NET should do. However, once customErrors are on the behavior changes and the IIS error pages take over.

This is just another reason to test your application in a staging environment or at least enable a full non-debug configuration on your application and test. This is also one reason why every app I have I usually have a test page that bombs on purpose to see what the error behavior is so I can quickly test it in the production environment and get an idea what errors do. Now that I think about it it might be prudent to set up a test page that checks both core errors and ‘service’ style errors from things like a JSON or XML service as well.

Anyway, I’d argue that this is a bug. While I agree that IIS should return error pages on completely unhandled errors and display the generic 500 error page, I do feel that IIS should always respect the output generated by the application if there’s no hard error that rippled all the way into the runtime (ie. no Application_Error handler).

Enter Response.TrySkipIisCustomErrors

There’s a solution to this problem with the deftly named TrySkipIisCustomErrors property on the Response object which is tailor made for this particular scenario. In a nutshell this property when set to true at any point in the request prevents IIS from injecting its custom error pages. This flag is new in ASP.NET 3.5 – so if you’re running 2.0 only you’re out of luck.

So in my error handler code above in Application_Error I can now add this flag and bingo I’m back in business getting my own custom error page to display:

// Display a generic error message
 Server.ClearError();
 Response.StatusCode = 500;
 
 Response.TrySkipIisCustomErrors = true; 
 
 MessageDisplay.DisplayMessage("Application Error",
     "We're sorry, but an unhandled error occurred on the server. " +
     StockMessage);

 return;

Same with my JsonCallbackMethodProcessHandler which returns JSON on errors:

public void WriteErrorResponse(string errorMessage, string stackTrace)
{
    CallbackException Error = new CallbackException();
    Error.message = errorMessage;
    Error.isCallbackError = true;
    Error.stackTrace = stackTrace;

    JSONSerializer Serializer = new JSONSerializer();
    string Result = Serializer.Serialize(Error);


    HttpResponse Response = HttpContext.Current.Response;
    Response.ContentType = ControlResources.STR_JsonContentType;

    Response.TrySkipIisCustomErrors = true;
    Response.StatusCode = 500;
    Response.Write(Result);
    HttpContext.Current.ApplicationInstance.CompleteRequest();
    //Response.End();
}

Thanks to Eilon Lipton, and Michael Campbell for pointing out that there is such a beast as Response.TrySkipIisCustomErrors – not where I would have looked for sure.

Good to Go

I’m glad to see that this flag is available, but we still have to remember to use this in every application that requires returning custom errors. I have tons of specialized handler implementations scattered across many (now idle) projects and finding all the places where this is going to take time.

It seems to me that the better default for TrySkipIisCustomErrors should be true to begin with. After all that’s why we run ASP.NET - to be control of our environment.

Posted in ASP.NET  IIS  

The Voices of Reason


 

DotNetKicks.com
April 30, 2009

# IIS 7 Error Pages taking over 500 Errors

You've been kicked (a good thing) - Trackback from DotNetKicks.com

Kazi Manzur Rashid
April 30, 2009

# re: IIS 7 Error Pages taking over 500 Errors

The ASP.NET MVC HandleErrorAttribute also followed the same.

Lex Li
April 30, 2009

# re: IIS 7 Error Pages taking over 500 Errors

What about setting Response.StatusCode to 200 and then write your custom content to the response?

Rick Strahl
April 30, 2009

# re: IIS 7 Error Pages taking over 500 Errors

@Lex - that works, but it's not a good solution for non-browser clients. You need to let the client know that an error occurred, especially if the client is an AJAX app or any sort of service client. Without the 500 failure the client will have no way of telling the difference between a failure and a valid request.

Nicholas
May 01, 2009

# re: IIS 7 Error Pages taking over 500 Errors

Rick,

I had a question similiar to this on Stackoverflow a while back:

http://stackoverflow.com/questions/434272/iis7-overrides-customerrors-when-setting-response-statuscode

Alexander Turlov
June 03, 2009

# re: IIS 7 Error Pages taking over 500 Errors

This solution does not work when IIS in "classic" mode. In "classic" the property is already true by default and it does not prevent IIS from adding its own message to my custom error message that completely ruins my markup. It's a nasty behavior of IIS and it seems there is no way to override its behavior. The only workaround that we've found so far was to configure IIS error files and use just empty files for that so IIS still adds the content from the file to my error page output but because it's empty it does not screw up the page. But it's really inconvenient and requires a lot of manual work to configure IIS. Any other thoughts?

Fabian Heussser
July 17, 2009

# re: IIS 7 Error Pages taking over 500 Errors

I'm in the progress of migrating a website to iis7 and read about your solution with 'TrySkipCustomErrors'.
Later in my search i came across this blog post about the iis7 custom error module and it's configuration: http://blogs.iis.net/ksingla/archive/2008/02/18/what-to-expect-from-iis7-custom-error-module.aspx
There is a description of a property 'existingResponse' (system.webserver/httpErrors) which defines what this module makes if the statuscode is not 200 and there is content in the response. This property is not accessible by the iis7 manager and has to be set by hand to "Passthrough", "Replace" or "Auto". The behaviour of the default mode "Auto" can be influenced with the property 'TrySkipCustomErrors'.
I have not verified this, but i think if you set this property to "Passthrough" you should accomplish the same as if you set 'TrySkipCustomErrors' to true.

Matt
September 02, 2009

# re: IIS 7 Error Pages taking over 500 Errors

once again your articles have saved me from hours of agro.

Thanks Rick, for the detailed explanation (one of my search queries finally paid off - there's a lot of other stuff out there which is just talking about using the proper codes, and custom error pages etc) and for the simple answer!!!

Tony
September 15, 2009

# re: IIS 7 Error Pages taking over 500 Errors

Just want to say THANK YOU Rick.

I've been fighting with IIS7 and the custom error page.
[Response.TrySkipIisCustomErrors = true;] helps me solve this problem.

Thanks!

ps. I just took on a ASP.NET developer role recently and I've start to recognize your blog & picture... =)

Waleed Eissa
September 22, 2009

# re: IIS 7 Error Pages taking over 500 Errors

I've been looking for a solution to this problem for a few days now, so, words can't describe how helpful this post to me.

Now I feel that I'm totally out of luck as the application I'm trying to deploy is an ASP.NET 2.0 application. Am I supposed to migrate the whole application to 3.5 just to get rid of this stupid bug in IIS7? WTF!!

Rick Strahl
September 22, 2009

# re: IIS 7 Error Pages taking over 500 Errors

@Waleed - I think this property is ASP.NET 2.0 SP1. It's a base feature so I don't think.NET 3.5 is required.

You can also run your virtual in Classic Mode which has the old behavior of IIS 6 and prior.

Waleed Eissa
September 29, 2009

# re: IIS 7 Error Pages taking over 500 Errors

Rick, thanks a lot for your reply. Luckily, Microsoft added something called redirectMode in ASP.NET SP2 (also 3.5 SP1). If you add redirectMode="ResponseRewrite" to the customErrors element in web.config, ASP.NET executes your custom error page instead of redirecting to it (as if you used Server.Transfer()). Unlike transferring from Application_Error in Global.asax, ASP.NET seems to handle the problem with IIS7 taking over the response (probably it internally calls some code similar to the code in Response.TrySkipIisCustomErrors, or even Response.TrySkipIisCustomErrors() itself).

You may check this blog post for more details:

http://www.colincochrane.com/post/2008/01/ASP-NET-Custom-Errors-Preventing-302-Redirects-To-Custom-Error-Pages.aspx

I still think your approach is more flexible (as you don't have to map the different HTTP errors to your error page) but in my case (having an ASP.NET 2.0 app), that really saved the day. I'll have a look on Response.TrySkipIisCustomErrors, they probably added this in ASP.NET 2.0 SP1 or SP2. Thanks again Rick.

Sergio Pereira
October 20, 2009

# re: IIS 7 Error Pages taking over 500 Errors

Thank you. You just prevented me from going crazy today.

Pavlo
November 04, 2009

# re: IIS 7 Error Pages taking over 500 Errors

Thanks!! Have got the same problem today.

Michael Hampton
December 14, 2009

# re: IIS 7 Error Pages taking over 500 Errors

This is just about exactly the solution I was looking for, but I need it for PHP! :( Back to Google, unless somebody knows something.

Vimal
January 20, 2010

# re: IIS 7 Error Pages taking over 500 Errors

Thanks a lot. I had exactly the same issue.

Keith
March 12, 2010

# re: IIS 7 Error Pages taking over 500 Errors

Hey Rick,
Thanks so much for this pointer. Saved the day. This is exactly what we do and what I needed.
Kind regards,

Benjamin
March 31, 2010

# re: IIS 7 Error Pages taking over 500 Errors

Thank you for this post. Like the many others here, you saved me from hours of frustration.

max
April 27, 2010

# re: IIS 7 Error Pages taking over 500 Errors

Response.TrySkipIisCustomErrors is available starting with net 2.0SP1

see this (bottom of the page):
http://msdn.microsoft.com/en-us/library/system.web.httpresponse.tryskipiiscustomerrors.aspx

Webdiyer
May 11, 2010

# re: IIS 7 Error Pages taking over 500 Errors

Thank you very much for solving my problem!

Lennart
July 03, 2010

# re: IIS 7 Error Pages taking over 500 Errors

Thank you, just what I was looking for.

AlexeyB
July 15, 2010

# re: IIS 7 Error Pages taking over 500 Errors

Thanks, that was exactly what I looked for! Seems like MS made ugly stub to its bug.

Markus Wolters
September 01, 2010

# re: IIS 7 Error Pages taking over 500 Errors

Thanks Rick for this post! I'm loving reading your posts, but this "beast" :-) saved my day! Can't figure out, why MS did such a thing... Greetings from Germany

Joe
September 10, 2010

# re: IIS 7 Error Pages taking over 500 Errors

Just wanted to say that is this affects other status codes as well. I had the same thing happen when I returned a 417 (ExpectationFailed). IIS took over the response with a message like "The response could not be completed because the expectation failed". So in our MVC result, I always set that to true if the code I am returning is not 200.

George
January 09, 2011

# re: IIS 7 Error Pages taking over 500 Errors

Just brilliant and thanks very much for sharing that.
I would not have known you solution in a million years time, you saved me after spending two days pulling my hair.

kiwi
June 13, 2011

# re: IIS 7 Error Pages taking over 500 Errors

For those stuck with "classic" asp (where you can't call TrySkipCustomErrors), Fabian's guess above is correct.

It seems that the *only* way to send your own (carefully crafted and non-security-violating) detailed error information *without* A. enabling "detailed errors" to the world (which is too open) and without B. resorting to custom pages (which have no idea what the error was) and *without* C. reverting to a non error status (hiding the real status) is to:

find the httpErrors node in your web.config file (or higher if desired)

add

existingResponse="PassThrough"

(or change existingResponse if it is there already)

You can then write eg

Response.Status = 500
Response.Write "the funglimator conflicted with the obviation policy becuase the secret sauce value supplied [removed] was not correct"

oh my -what a mission!

David Hogue
October 31, 2011

# re: IIS 7 Error Pages taking over 500 Errors

Hey, just wanted to say thanks for writing this up. We ran into the same issue and this got me pointed in the right direction pretty quickly.

Dating
January 26, 2012

# re: IIS 7 Error Pages taking over 500 Errors

Just wanted to say that is this affects other status codes as well.
You've saved us alot of work with this article.

Atif Rehman
February 16, 2012

# re: IIS 7 Error Pages taking over 500 Errors

Fixed this problem by editing the Web.config and applicationhost.config.
C:\Users\<USERNAME>\Documents\IISExpress\config or right click the IIS Express icon on the tray and view all application and click the desired website and then click the configuration file link.

Comment out
<error statusCode="500" prefixLanguageFilePath="%IIS_BIN%\custerr" path="500.htm" />
Set the parent node to <httpErrors errorMode="DetailedLocalOnly"

Set the httpErrors to Allow
<section name="httpErrors" overrideModeDefault="Allow" />

Change your web project web.config HttpErrors
<httpErrors errorMode="Custom" existingResponse="Auto">

That should fix it, its basically saying that "I dont want IIS Express to override the configuration of my site".

Thanks
Atif

James Hutchison
June 27, 2012

# re: IIS 7 Error Pages taking over 500 Errors

+1 for what Atif Rehman has posted!

By default httpErrors at the machine level hase overrideModeDefault set to Deny!

Why? Why? Why is this a default?

Anyway, by either editing applicationHost.config setting httpErrors overrideModeDefault to allow or using the FeatureDelegation in IISManager, setting to allow, everything springs into life!

James

Bob W
September 12, 2012

# re: IIS 7 Error Pages taking over 500 Errors

Rick, Thanks for the article but I have one question. I've tried the TrySkipIisCustomErrors trick but when an exception is thrown in the application_start, application_beginrequest or application_end request setting this property to true still won't render my custom errors or httpErrors defined in my Web.config while in integrated mode. Do you have any thoughts or experiences on this?

Rick Strahl
September 12, 2012

# re: IIS 7 Error Pages taking over 500 Errors

@Bob - I wasn't aware of that, but it makes sense for Application_Start errors not to trigger it. The application isn't configured yet - Application_Start happens as part of the Application's constructor logic, so the instance is not actually live yet. Application_Start is effectively a delegate that's fired, but it's not actually part of the HttpApplication instance.

Application_BeginRequest/EndRequest should work however, and I'm pretty sure that it does work there as I have code in there in most of my apps and I see errors from there. You sure that it's not working from those events?

Bob W
September 13, 2012

# re: IIS 7 Error Pages taking over 500 Errors

Thanks for your reply Rick.

To answer your question, I believe it’s not working..

At its simplest form this is what I did (to avoid custom IIS handlers or modules… Straight stock asp.net and mvc.): Create a stock VS 2010 MVC 3 web application and accept IIS default settings for running local IIS. I added the basic <customError> and <htmlErrors> settings for handling 404 and 500 errors in the web.config to target a specific static Error.HTML page relevant to the root of the web application. I put the following code in the application_start, application_beginrequest and application_endrequest.
//syntax is probably wrong. 
try{
    throw new exception();
}
catch (exception ex){
    Server.ClearError();
    Response.TrySkipIisCustomErrors = true;
    Response.StatusCode = 500; // target response code setup begin/end request and start app fails
    throw ex; // comment this out w/ different results in integrated mode. 
}

As you mentioned (while in integrated mode) the application_start behavior acted as you might expect because the application isn’t configured yet.

However while still in integrated mode throwing an exception in the begin or end request will not render my static error page defined in my web.conf. This will work fine while running in classic mode (Static page will render as I would expect).

This is driving me f’n nuts any insight would be appreciated.

OutOfTouch
December 10, 2012

# re: IIS 7 Error Pages taking over 500 Errors

Rick,

What is the CallbackException in your posted code above?
Is it your own class? If it is, would you mind sharing the code, if that is possible.

Regards,

OutOfTouch

Rick Strahl
December 10, 2012

# re: IIS 7 Error Pages taking over 500 Errors

@OutOfTouch - it doesn't matter in this context as it's just an example. CallbackException is a custom class I use that returns error information to the client consistently. Any error usually uses this mechanism.

Marc Wilson
February 28, 2014

# re: IIS 7 Error Pages taking over 500 Errors

I have what *might* be a related issue. Weirdly, it's only just started happening.

Background:

I support a website which has active content supplied by a back-end platform which is accessed via "Classic" ASP (and no- it doesn't support ASP.net as yet). Essentially, there's a COM object called from an ASP page that produces the page content. Because the syntax of this is hideously ugly, and for a couple of other reasons, it sits behind a set of rewrite rules using Helicon's ISAPI_rewrite.

Because the site-owner wants to know what's being searched for and not found, there's a catch all rule at the end of the rewrite rules, that "catches" any potential 404 and fires it to
default.asp?category=mar-runs&service=custom404&oqs={original query string}

The last act of this default.asp, *if* the service is "custom404" is to set the response code to 404. Which in turn triggers the 404 custom page and shows a nice site-formatted page to the end user.

This is a bit convoluted, but I couldn't find another way to both capture what was being sought *and* return a proper 404 (rather than a "soft" 404. which Google hates).

I also have some rewrite rules that create a 410 "gone" status- this is desired to be more forceful than 404; this page has been deliberately removed, it's not coming back so don't even ask!

I trigger this by rewriting the gone pages to "/page.gone" with a rewrite rule and appending various info into a query string - there's then a subsequent rule that returns a 410 response to "page.gone" - which goes to custom410.asp and shows a nicely-formatted page.

This all worked nicely (if a little kludgily) until today, when for some reason the 404 is being supplanted by a 500.19 status.

The detailed status says:
Error Code    0x800700b7
Config Error    Cannot add duplicate collection entry of type 'error' 
with combined key attributes 'statusCode, subStatusCode' respectively set to '404, -1' 


- which looks as though it's considering 410 to be part of 404.

The section of the config is:
<httpErrors>
            <error statusCode="404" path="/custom404.asp" responseMode="ExecuteURL" />
            <error statusCode="410" path="/custom410.asp" responseMode="ExecuteURL" />
</httpErrors>


Which looks fine to me. Am I missing something subtle, here?

If there's a better method, I'm keen to hear it. The existingResponse="PassThrough" looks promising: does that mean I can set the status *and* the page content in my default.asp and it will just send that error status without using the standard error page?

If so, that's probably a better solution for me, as it keeps it all inside my platform.

Billa
March 03, 2014

# re: IIS 7 Error Pages taking over 500 Errors

I am having a similar problem described here http://stackoverflow.com/questions/22144777/asp-net-404-redirect-not-working

But it is not working for me.

Anurag Sharma
October 27, 2014

# re: IIS 7 Error Pages taking over 500 Errors

This code works file for 500 error code but not works for other error codes like 401, i have a rest web service and i have to set the response code according to the functionality.

Rick Strahl
October 28, 2014

# re: IIS 7 Error Pages taking over 500 Errors

@Anurag - Make sure you're not redirecting some errors elsewhere in the system. I think if you're using ASP.NET AUTH anywhere in the system it takes over the errors internally and handles redirection to authentication forms/endpoints. Technically a 401 isn't an error - it's a request to authenticate. Only if IIS gets failure authentication is it considered an error.

West Wind  © Rick Strahl, West Wind Technologies, 2005 - 2024