Using a combination of named locations along with the error_page directive, we can make it so nginx automatically serves error pages from a directory structure with support for wildcard/catch-all files. For example the handler for status code 503 will check for

  • /errors/error_503.html
  • /errors/error_50x.html
  • /errors/error_5xx.html
  • /errors/error.html

Creating the helper maps

First off, we'll need to use the map directive to help us make the above lookup work. You will need to place these lines within the server block (on CentOS 7 you can pop these into a file in /etc/nginx/conf.d/

map $status $status_category {
        "~^(?<code>(?<prefix>(?<category>[1-5])[0-9])[0-9])$" "$category";
}

map $status $status_prefix {
        "~^(?<code>(?<prefix>(?<category>[1-5])[0-9])[0-9])$" "$prefix";
}

Create the error handler location block

Now we will create our special named location which will handle finding all of our error pages. You can put this block within any server or location block:

location @error {
        try_files

        /errors/error_$status.html
        /errors/error_${status_prefix}x.html
        /errors/error_${status_category}xx.html
        /errors/error.html

}

Enable the error handler

For any error you want to use your custom error pages for, simply add this directive to your server or location blocks:

error_page 503 @error;
error_page 500 501 502 504 505 @error; # you can do all the error codes in one line too, it will still serve the correct error page based on the lookup paths we defined for each status code

Final Thoughts

The above works very well as long as at least one of the files (error_503.html, error_50x.html, error_5xx.html, or error.html) and it will preserve the original status code.

If none of the files exist, it will still return the correct status code but the body will be blank. I have not been able to figure out a way to get Nginx to fallback the default error page without creating location blocks for each individual error (you can't do =$status in the try_files directive).

A hacky workaround might involve using the echo or somehow making it so the @error can redirect to another named block that did not inherit the custom error_page directives. For now, if you don't have a custom error page defined, just don't include that status in the error_page directives.

If you have any suggestions to make this more ergonomic, please let me know!