Every Web-Site Administrator or Web Developer has
seen "404 - File not found" , "401 - Unauthorized" or "500 - Server Error"
messages in his browser. This article will help you understand how and why IIS
generates these errors and how they can be configured.
You might think that generating error messages
doesn't seem to justify a full blown article. But there is more to errors than
meets the eye. Error messages are a touchy topic, because every error reveals
more about your web-site than you might want. And the more information somebody
can gather about your site the likelier it is that you get hacked. Just search
for "google hacking" or "cross-site scripting" and you will find a wealth of
information on this topic.
But error messages are also a valuable tool to
troubleshoot problems. Developers and Web-Site Administrators want as much
detail as possible when an error occurs. Ideally the error message would already
give recommendations on how to fix the problem. Here is how IIS7 addresses these
fundamentally opposed goals.
Status codes between 400 and 500 specify an error
made by the client, e.g. bad syntax or a request to a resource that doesn't
exist. You can try this by requesting a bogus URL from the web-site of your
choice, for example: http://<IIS7Server>/this_resource_does_not_exist.
You get a "404 - File not found" error.
Status codes starting with 500 are errors caused by
the server. The most common causes for 500 errors on IIS systems are
It is important to notice that browsers like IE often replace errors returned
from a web server with their own. This sometimes makes troubleshooting harder.
In Internet Explorer you can turn this off. Go to the "Tools" menu, select
"Internet Options", click the "Advanced" tab and find the "Show friendly HTTP
error messages" check box and uncheck it. If you want to see the raw response
you have to use HTTP tools like WFETCH in the IIS 6.0 Resource Kit (see "Related
Links").
There are two things that can happen when the httpError module (custerr.dll)
encounters an error.
Custom errors are error pages that the regular users of your web-site will
see. They contain a brief error description why the error happened, but nothing
else. Here is the custom error generated when you request a resource that does
not exist, for example
http://<IIS7Server>/this_resource_does_not_exist
Detailed errors are intended for local administrators and developers. They
are supposed to give a wealth of information that can help to immediately fix
the problem. Here is an example of the same request, but now returning a
Detailed Error:
This is dangerous of course, because Detailed Errors contain information about
the inner workings of your web-site. Only trusted personal should see a Detailed
Error. The only way to ensures this is to only generate a detailed error if the
request comes from the local machine. As soon as the request is not local a
custom error is generated. Let's have a look at the following flow diagram:
Data Flow
First: Error check
The httpError module receives a notification if a
response is about to be sent (RQ_SEND_RESPONSE notification). The httpError
module checks the status code of this response and immediately returns if the
status code is not greater than 400.
Second: Custom Error or Detailed Error
The next check is determined by the request origin
(is the request a local request or does it come from remote) and the setting of
the overrideMode property. The overrideMode is set to remoteOnly which means
that Custom Errors are generated for every remote request. If overrideMode is
set to on then all error responses will become Custom Error. If overrideMode is
set to Off all error responses will become Detailed Errors. The following table
clarifies this behavior:
|
overrideMode setting
|
Request origin |
Action |
|
RemoteOnly (default) |
Local |
Detailed Error |
|
RemoteOnly (default) |
Remote |
Custom Error |
|
On |
Local |
Custom Error |
|
On |
Remote |
Custom Error |
|
Off |
Local |
Detailed Error |
|
Off |
Remote |
Detailed Error |
Note: The overrideMode and its arguments will be
renamed to errorMode after Beta2. Here is how the table if you have a post-Beta2
build of Windows Vista.
|
errorMode |
Request origin |
Action |
|
detailedLocalOnly (default) |
Local |
Detailed Error |
|
detailedLocalOnly (default) |
Remote |
Custom Error |
|
custom |
Local |
Custom Error |
|
custom |
Remote |
Custom Error |
|
detailed |
Local |
Detailed Error |
|
detailed |
Remote |
Detailed Error |
- If the httpError module determines that a Custom Error needs to be generated
it looks into its configuration if a matching error can be found. If a match is
found it send the static file, redirects the request or executes the URL
specified. If no match can be found IIS7 send a very basic one-line message
containing the status code. The next section explains the Custom Error
configuration in detail.
- If custerr.dll determines that a Detailed Error needs to be generated
another check is needed. IIS7 doesn't touch the response if a module overrode
the entity of the response with its own error description. It might contain
valuable information. ASP.NET is a good example. The entity of an ASP.NET error
response might contain the exception stack and its own error description. A
Detailed Error is only generated if the entity body of the response is empty.
<httpErrors> Configuration
Here is the IIS7 custom error section that you get on a clean install:
<httpErrors>
<error statusCode="401" prefixLanguageFilePath="c:\inetpub\custerr" path="401.htm" />
<error statusCode="403" prefixLanguageFilePath="c:\inetpub\custerr" path="403.htm" />
<error statusCode="404" prefixLanguageFilePath="c:\inetpub\custerr" path="404.htm" />
<error statusCode="405" prefixLanguageFilePath="c:\inetpub\custerr" path="405.htm" />
<error statusCode="406" prefixLanguageFilePath="c:\inetpub\custerr" path="406.htm" />
<error statusCode="412" prefixLanguageFilePath="c:\inetpub\custerr" path="412.htm" />
<error statusCode="500" prefixLanguageFilePath="c:\inetpub\custerr" path="500.htm" />
<error statusCode="501" prefixLanguageFilePath="c:\inetpub\custerr" path="501.htm" />
<error statusCode="502" prefixLanguageFilePath="c:\inetpub\custerr" path="502.htm" />
</httpErrors>
You see that if the status code of a response is 401, IIS will return a file
named 401.htm.
Sub-Status Codes
Many HTTP errors have a sub-status. The IIS7 default Custom Errors
configuration doesn't differentiate based sub-status codes. It sends you the
same Custom Error page if you enter the wrong credentials (401.1) or if you get
access denied based on invalid rights to access a file (401.3). You can see the
different sub-status codes in your log files or via Detailed Errors. Here is a
list of the different 404 sub-status codes that IIS7 produces.
|
Status |
Description |
|
404.1 |
Site couldn't be found |
|
404.2 |
Denied by Policy. The request ISAPI or CGI program is not allowed in the
Restriction List. |
|
404.3 |
The static file handler didn't have the file in its MimeMap and therefore
rejected the request. |
|
404.4 |
No handler was found to serve the request. |
|
404.5 |
The Request Filtering Module rejected an URL sequence in the
request. |
|
404.6 |
The Request Filtering Module denied the HTTP verb of the
request. |
|
404.7 |
The Request Filtering module rejected the file extension of the
request. |
|
404.8 |
The Request Filtering module rejected a particular URL segment (characters
between two slashes). |
|
404.9 |
IIS rejected to serve a hidden file. |
|
404.10 |
The Request Filtering module rejected a too long header. |
|
404.11 |
The Request Filtering module rejected a request that was double
escaped. |
|
404.12 |
The Request Filtering module rejected a request that contained high bit
characters. |
|
404.13 |
The Request Filtering module rejected a request that was too long (request +
entity body). |
|
404.14 |
The Request Filtering module rejected a request with a too long URL.
|
|
404.15 |
The Request Filtering module rejected a request with a too long query
string. |
You can configure the httpErrors section to show a Custom Error for
particular sub-status codes. If you add the following line to your httpErrors
configuration section IIS7 will return 404_3.htm if a file with a file extension
is requested that isn't included in the IIS7 MimeMap (<staticContent>
configuration section).
<error statusCode="404" subStatusCode="3" prefixLanguageFilePath="c:\inetpub\custerr" path="404_3.htm" />
How to make the example work:
- Add the entry above to your httpErrors configuration section.
- Create a file named 404_3.htm in your c:\inetpub\custerr\en-us directory.
- Create a file named test.yyy in you c:\inetpub\wwwroot directory.
- Now request http://localhost/test.yyy
The file extension .yyy is not part of the IIS7 MimeMap and the static file
handler will not serve it.
New in IIS7: language specific custom errors
Every newer browser includes the language of the client as a request header.
Here an example how this header might look like
Accept-Language: en-us
The syntax and registry of accepted languages is specified in RFC1766.
When generating an error IIS7 takes this header into account when it looks
for the custom error file it returns. It generates the path for the custom error
using the following logic:
prefixLanguageFilePath configuration setting (for example
c:\inetpub\custerr)+
Accept-Language header sent by the client (for example
en-us) +
Path configuration setting (for example 404.htm)
Example:
If the browser sends a request for an non-existing resource and the
"Accept-Language" header has the value of "en-us" the file that gets returned
will be "c:\inetpub\custerr\en-us\404.htm".
If you are from Germany like my poor soul you want your error messages in
German. To do this you have to install the Windows Vista Language Pack for
German. This will create the c:\inetpub\custerr\de-DE directory with custom
error files in it. Now if the browser sends the "Accept-Language" header with
the value of "de-DE the file that gets returned will be
"c:\inetpub\custerr\de-DE\404.htm".
IIS7 will always fall back to the system language if the directory "de-DE"
doesn't exist.
Note: Internet Explorer allows you to configure the Accept-Language header.
Go to "Tools" - "Internet Option", select the "General" tab and click the
"Languages" button.
Custom Error Options
In the above examples IIS7 sends the contents of the file as the custom error
response. IIS7 has two other ways to respond to an error. By executing an URL or
by redirecting the request.
ExecuteUrl
If you want to do a bit more in your custom error, e.g. sending an e-mail or
logging the error to a database, you can execute an url. This allows you to
execute dynamic content like an ASP.NET page. In the example below I replace the
404 custom error. Now IIS7 will execute /404.aspx whenever a 404 error occurs.
<httpErrors>
<!-- default custom error for 401 errors -->
<!-- <error statusCode="404" prefixLanguageFilePath="c:\inetpub\custerr" path="404.htm" />-->
<!-- ExecuteURL replaces default file response mode -->
<error statusCode="404" path=/404.aspx" responseMode="ExecuteURL"/>
<error statusCode="403" prefixLanguageFilePath="c:\inetpub\custerr" path="403.htm" />
<error statusCode="404" prefixLanguageFilePath="c:\inetpub\custerr" path="404.htm" />
<error statusCode="405" prefixLanguageFilePath="c:\inetpub\custerr" path="405.htm" />
<error statusCode="406" prefixLanguageFilePath="c:\inetpub\custerr" path="406.htm" />
<error statusCode="412" prefixLanguageFilePath="c:\inetpub\custerr" path="412.htm" />
<error statusCode="500" prefixLanguageFilePath="c:\inetpub\custerr" path="500.htm" />
<error statusCode="501" prefixLanguageFilePath="c:\inetpub\custerr" path="501.htm" />
<error statusCode="502" prefixLanguageFilePath="c:\inetpub\custerr" path="502.htm" />
</httpErrors>
A word of caution: For architectural reasons IIS7 can only execute the URL if
it is located in the same Application Pool. Use the redirect feature if you need
to execute a Custom Error in a different Application Pool.
IIS7 can also return a 302 Redirect to the browser
when a particular error occurs. Redirect is great if you have a web farm for
example you can redirect all your errors to a central location that you closely
monitor. Here an example:
<section name="httpErrors" type="System.WebServer.Configuration.HttpErrorsSection,
System.ApplicationHost, Version=7.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
overrideModeDefault="Deny" />
This is not without risk however. responseMode="File" (which is the default)
allows you to specify every file on the disk. This might not work for you if you
are very security conscious.
A workable scenario might be to only allow the delegation of the overrideMode
(errorMode after Beta2) setting. This enables a developer to receive Detailed
Errors for his application even if he is remote. All he has to do is to set
overrideMode="Off" (errorMode="detailed" after Beta2). Here is how to configure
this scenario:
Allow the delegation of the httpErrors section:
<section name="httpErrors" type="System.WebServer.Configuration.HttpErrorsSection,
System.ApplicationHost, Version=7.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
overrideModeDefault="Allow" />
Second, you can go to the <httpErrors> section in
applicationHost.config and change it so that only overrideMode is delegated:
<httpErrors lockAllAttributesExcept="overrideMode" lockElements="error">
<error statusCode="404" prefixLanguageFilePath="E:\inetpub\custerr" path="404.htm" />
<error statusCode="401" prefixLanguageFilePath="E:\inetpub\custerr" path="401.htm" />
<error statusCode="403" prefixLanguageFilePath="E:\inetpub\custerr" path="403.htm" />
<error statusCode="405" prefixLanguageFilePath="E:\inetpub\custerr" path="405.htm" />
<error statusCode="406" prefixLanguageFilePath="E:\inetpub\custerr" path="406.htm" />
<error statusCode="412" prefixLanguageFilePath="E:\inetpub\custerr" path="412.htm" />
<error statusCode="500" prefixLanguageFilePath="E:\inetpub\custerr" path="500.htm" />
<error statusCode="501" prefixLanguageFilePath="E:\inetpub\custerr" path="501.htm" />
<error statusCode="502" prefixLanguageFilePath="E:\inetpub\custerr" path="502.htm" />
</httpErrors>
Summary
Custom and Detailed Errors are powerful IIS7 features. They help you to
troubleshoot problems without compromising the security of your IIS7 Server.
Plentiful configuration options help you to custom tailor the experience of your
users. But most importantly: playing around with it is fun.