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!