The php part of this article is based on my Advanced WordPress 404.php article from 2008. Many of the following ideas came out of the research performed to enumerate every single Apache ErrorDocument, including learning how to view the defaults and many cool tricks for htaccess.

The PHP HTTP ErrorDocument Handler

Just save this as /err.php or whatever. The best is to put it in a cgi-bin script-alias directory under your DOCUMENT_ROOT like /cgi-bin/e.php but most people don't know how. That way you can setup some advanced stuff in a /cgi-bin/.htaccess file. If you are interested in locking it down, I recommend reading Securing php.ini and php-cgi with .htaccess.

Advantages and Reasons for Using

Fast, HTTP Protocol Compliance, protection. If you are reading this article, you already know and just want to check out the code!

@ini_set('memory_limit', '64M');
@ini_set('display_errors', 'Off');

function print_error_page()

  $status_reason = array(
  100 => 'Continue',
  101 => 'Switching Protocols',
  102 => 'Processing',
  200 => 'OK',
  201 => 'Created',
  202 => 'Accepted',
  203 => 'Non-Authoritative Information',
  204 => 'No Content',
  205 => 'Reset Content',
  206 => 'Partial Content',
  207 => 'Multi-Status',
  226 => 'IM Used',
  300 => 'Multiple Choices',
  301 => 'Moved Permanently',
  302 => 'Found',
  303 => 'See Other',
  304 => 'Not Modified',
  305 => 'Use Proxy',
  306 => 'Reserved',
  307 => 'Temporary Redirect',
  400 => 'Bad Request',
  401 => 'Unauthorized',
  402 => 'Payment Required',
  403 => 'Forbidden',
  404 => 'Not Found',
  405 => 'Method Not Allowed',
  406 => 'Not Acceptable',
  407 => 'Proxy Authentication Required',
  408 => 'Request Timeout',
  409 => 'Conflict',
  410 => 'Gone',
  411 => 'Length Required',
  412 => 'Precondition Failed',
  413 => 'Request Entity Too Large',
  414 => 'Request-URI Too Long',
  415 => 'Unsupported Media Type',
  416 => 'Requested Range Not Satisfiable',
  417 => 'Expectation Failed',
  422 => 'Unprocessable Entity',
  423 => 'Locked',
  424 => 'Failed Dependency',
  426 => 'Upgrade Required',
  500 => 'Internal Server Error',
  501 => 'Not Implemented',
  502 => 'Bad Gateway',
  503 => 'Service Unavailable',
  504 => 'Gateway Timeout',
  505 => 'HTTP Version Not Supported',
  506 => 'Variant Also Negotiates',
  507 => 'Insufficient Storage',
  510 => 'Not Extended'

  $status_msg = array(
  400 => "Your browser sent a request that this server could not understand.",
  401 => "This server could not verify that you are authorized to access the document requested.",
  402 => 'The server encountered an internal error or misconfiguration and was unable to complete your request.',
  403 => "You don't have permission to access %U% on this server.",
  404 => "We couldn't find <abbr title='%U%'>that uri</abbr> on our server, though it's most certainly not your fault.",
  405 => "The requested method is not allowed for the URL %U%.",
  406 => "An appropriate representation of the requested resource %U% could not be found on this server.",
  407 => "An appropriate representation of the requested resource %U% could not be found on this server.",
  408 => "Server timeout waiting for the HTTP request from the client.",
  409 => 'The server encountered an internal error or misconfiguration and was unable to complete your request.',
  410 => "The requested resource %U% is no longer available on this server and there is no forwarding address. Please remove all references to this resource.",
  411 => "A request of the requested method GET requires a valid Content-length.",
  412 => "The precondition on the request for the URL %U% evaluated to false.",
  413 => "The requested resource %U% does not allow request data with GET requests, or the amount of data provided in the request exceeds the capacity limit.",
  414 => "The requested URL's length exceeds the capacity limit for this server.",
  415 => "The supplied request data is not in a format acceptable for processing by this resource.",
  416 => 'Requested Range Not Satisfiable',
  417 => "The expectation given in the Expect request-header field could not be met by this server. The client sent <code>Expect:</code>",
  422 => "The server understands the media type of the request entity, but was unable to process the contained instructions.",
  423 => "The requested resource is currently locked. The lock must be released or proper identification given before the method can be applied.",
  424 => "The method could not be performed on the resource because the requested action depended on another action and that other action failed.",
  425 => 'The server encountered an internal error or misconfiguration and was unable to complete your request.',
  426 => "The requested resource can only be retrieved using SSL. Either upgrade your client, or try requesting the page using https://",
  500 => 'The server encountered an internal error or misconfiguration and was unable to complete your request.',
  501 => "This type of request method to %U% is not supported.",
  502 => "The proxy server received an invalid response from an upstream server.",
  503 => "The server is temporarily unable to service your request due to maintenance downtime or capacity problems. Please try again later.",
  504 => "The proxy server did not receive a timely response from the upstream server.",
  505 => 'The server encountered an internal error or misconfiguration and was unable to complete your request.',
  506 => "A variant for the requested resource <code>%U%</code> is itself a negotiable resource. This indicates a configuration error.",
  507 => "The method could not be performed.  There is insufficient free space left in your storage allocation.",
  510 => "A mandatory extension policy in the request is not accepted by the server for this resource."

  // Get the Status Code
  $sc = (!isset($_GET['error']) ? 404 : $_GET['error']);


  // Redirect to server home if called directly or if status is under 400
  if( ( (isset($_SERVER['REDIRECT_STATUS']) && $_SERVER['REDIRECT_STATUS'] == 200) && (floor($sc / 100) == 3) )
     || (!isset($_GET['error']) && $_SERVER['REDIRECT_STATUS'] == 200)  )
      @header("Location: http://{$_SERVER['SERVER_NAME']}",1,302);

  // Check range of code or issue 500
  if (($sc < 200) || ($sc > 599)) $sc = 500;

  // Check for valid protocols or else issue 505
  if (!in_array($_SERVER["SERVER_PROTOCOL"], array('HTTP/1.0','HTTP/1.1','HTTP/0.9'))) $sc = 505;

  // Get the status reason
  $reason = (isset($status_reason[$sc]) ? $status_reason[$sc] : '');

  // Get the status message
  $msg = (isset($status_msg[$sc]) ? str_replace('%U%', htmlspecialchars(strip_tags(stripslashes($_SERVER['REQUEST_URI']))), $status_msg[$sc]) : 'Error');

  // issue optimized headers (optimized for your server)
  @header("{$_SERVER['SERVER_PROTOCOL']} {$sc} {$reason}", 1, $sc);
  if( @php_sapi_name() != 'cgi-fcgi' ) @header("Status: {$sc} {$reason}", 1, $sc);

  // A very small footprint for certain types of 4xx class errors and all 5xx class errors
  if (in_array($sc, array(400, 403, 405)) || (floor($sc / 100) == 5))
    @header("Connection: close", 1);
    if ($sc == 405) @header('Allow: GET,HEAD,POST,OPTIONS', 1, 405);

  echo "<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">n<html>";
  echo "<head>n<title>{$sc} {$reason}</title>n<h1><a id="reason" href="#reason">{$reason}</a></h1>n<p>{$msg}<br />n</p>n";

function askapache_global_debug()
  global $_GET,$_POST,$_ENV,$_SERVER;  $g=array('_ENV','_SERVER','_GET','_POST');
  array_walk_recursive($g, create_function('$n','global $$n;if( !!$$n&&ob_start()&&(print "[ $"."$n ]n")&&array_walk($$n,
    create_function('$v,$k', 'echo "[$k] => $vn";'))) echo "<"."p"."r"."e>".htmlspecialchars(ob_get_clean())."<"."/"."pr"."e>";') );

echo "</body>n</html>";
echo ob_get_clean();

Note: If you are installing this on a non-linux/non-apache machine/server, you will need to read your products documentation for custom error documents. It will work on any machine that can run php.

Htaccess ErrorDocument Tips

The thing is, how do you setup your website to use this php file to be able to handle all those HTTP Status Codes gracefully? You just need to configure your server to use that php file for any Status Codes you want. If you are building an ErrorDocument handling system for a server-wide, multi-site, setup, you will want to instead use the method I use. Instead of using a separate language like PHP, Python, Ruby, Perl, etc, to handle errors, I rely on the very safe and fast SSI method. I detailed the advanced ErrorDocument SSI (.htaccess or httpd.conf)..

If you instead like most of us, you will be setting this up for 1 site, or 1 DOCUMENT_ROOT serving virtual hosts. For that the best method is to modify your .htaccess file.

Using Redirect in .htaccess to trigger Errors

This is one of my all time favorite discoveries from my apache studies. It's documented elsewhere on this site, but if you want to test it out, just request Of course the error handling that I have in place is quite nice.

Redirect 400 /show-error-400
Redirect 401 /show-error-401
Redirect 402 /show-error-402
Redirect 403 /show-error-403
Redirect 405 /show-error-405
Redirect 406 /show-error-406
Redirect 407 /show-error-407
Redirect 408 /show-error-408
Redirect 409 /show-error-409
Redirect 410 /show-error-410
Redirect 411 /show-error-411
Redirect 412 /show-error-412
Redirect 413 /show-error-413
Redirect 414 /show-error-414
Redirect 415 /show-error-415
Redirect 416 /show-error-416
Redirect 417 /show-error-417
Redirect 418 /show-error-418
Redirect 419 /show-error-419
Redirect 420 /show-error-420
Redirect 421 /show-error-421
Redirect 422 /show-error-422
Redirect 423 /show-error-423
Redirect 424 /show-error-424
Redirect 425 /show-error-425
Redirect 426 /show-error-426
Redirect 500 /show-error-500
Redirect 501 /show-error-501
Redirect 502 /show-error-502
Redirect 503 /show-error-503
Redirect 504 /show-error-504
Redirect 505 /show-error-505
Redirect 506 /show-error-506
Redirect 507 /show-error-507
Redirect 508 /show-error-508
Redirect 509 /show-error-509
Redirect 510 /show-error-510

Powerful Mod_Rewrite Trick

Here's how to combine the power of mod_rewrites ability to parse requests and environment variables with the above Redirect trick to trigger a specific ErrorDocument based on the query_string parameter error. This trick is only on, very powerful trick if you need to force ErrorDocuments.

RewriteCond %{QUERY_STRING} error=([4|5][0-9][0-9]) [NC]
RewriteCond %{QUERY_STRING} !404
RewriteRule . /show-error-%1 [L]

ErrorDocument Example for .htaccess

So if you save the php file as err.php in your DOCUMENT_ROOT, these are the htaccess commands that will enable its use.

The addition of the ?error=num should be unneccessary on a good linux machine, it's a way for lesser OS's and webhosts to still be able to use errordocuments. Basically Apache handles ErrorDocuments by setting special DEBUGGING variables (Start with REDIRECT_) so it's very easy to determine the STATUS CODE by just viewing $_SERVER['REDIRECT_STATUS']. If a recursive type of redirect is going on, you may see $_SERVER['REDIRECT_REDIRECT_STATUS']. Dumb (consistently) OS's like a Windows server almost always have problems with things like that, because they don't give a hoot about POSIX or standards, why should they when no one can view their code anyway. Here are some more details on the REDIRECT_STATUS and other ways to use these variables.

If you want to learn how to enumerate and view the different variables that are in your Apache environment, I think I have the best tutorial on the planet for how to do this with PHP and mod_rewrite with mod_headers. That article is the basis for anyone who is hired to do mod_rewrites on a new server without root access. I would say that one article will inform you more about mod_rewrite then any other article on this site.

# ErrorDocument: In the event of a problem or error, what the server will return to the client. URLs
# can begin with a / for local web-paths (relative to DocumentRoot), or be a full URL which the client
# can resolve. Alternatively, a message can be displayed.  If a malformed request is detected, normal
# request processing will be immediately halted and the internal error message returned.
# Prior to version 2.0, messages were indicated by prefixing them with a
# single unmatched double quote character.
# The special value default can be used to specify Apache's simple hardcoded message and
# will restore Apache's simple hardcoded message.
ErrorDocument 400 /err.php?error=400
ErrorDocument 401 /err.php?error=401
ErrorDocument 402 /err.php?error=402
ErrorDocument 403 /err.php?error=403
ErrorDocument 404 /err.php?error=404
ErrorDocument 405 /err.php?error=405
ErrorDocument 406 /err.php?error=406
ErrorDocument 407 /err.php?error=407
ErrorDocument 408 /err.php?error=408
ErrorDocument 409 /err.php?error=409
ErrorDocument 410 /err.php?error=410
ErrorDocument 411 /err.php?error=411
ErrorDocument 412 /err.php?error=412
ErrorDocument 413 /err.php?error=413
ErrorDocument 414 /err.php?error=414
ErrorDocument 415 /err.php?error=415
ErrorDocument 416 /err.php?error=416
ErrorDocument 417 /err.php?error=417
ErrorDocument 422 /err.php?error=422
ErrorDocument 423 /err.php?error=423
ErrorDocument 424 /err.php?error=424
ErrorDocument 426 /err.php?error=426
ErrorDocument 500 /err.php?error=500
ErrorDocument 501 /err.php?error=501
ErrorDocument 502 /err.php?error=502
ErrorDocument 503 /err.php?error=503
ErrorDocument 504 /err.php?error=504
ErrorDocument 505 /err.php?error=505
ErrorDocument 506 /err.php?error=506
ErrorDocument 507 /err.php?error=507
ErrorDocument 510 /err.php?error=510

PHP ErrorDocument HTTP PHP