When configuring Nginx to handle error pages with fallback locations, many developers encounter a specific challenge: the system works perfectly for GET requests but fails when handling POST requests that result in errors. This occurs because Nginx preserves the original request method when processing error_page directives.
server {
root /var/www/someserver.com;
location ~* ^/(robots\.txt)$ {
error_page 404 = @default;
}
location @default {
root /var/www/default;
}
}
For error pages, a naive implementation might look like this:
server {
root /var/www/someserver.com;
error_page 404 /404.html;
location ~* ^/(404\.html)$ {
error_page 404 = @default;
}
location @default {
root /var/www/default;
}
}
This works for direct requests to /404.html but fails for actual 404 errors because Nginx's error_page directive doesn't properly chain to the fallback location.
Here's the complete solution that handles both GET and POST requests correctly:
server {
root /var/www/someserver.com;
# Handle errors via named locations
error_page 404 = @notfound;
error_page 500 502 504 = @server_error;
error_page 503 = @maintenance;
location @notfound {
# Convert POST to GET for static files
if ($request_method = POST) {
return 307 /404.html;
}
try_files /404.html /../default/404.html =404;
}
location @server_error {
if ($request_method = POST) {
return 307 /500.html;
}
try_files /500.html /../default/500.html =500;
}
location @maintenance {
if ($request_method = POST) {
return 307 /503.html;
}
try_files /503.html /../default/503.html =503;
}
# Handle direct requests to error pages
location ~* ^/(40[34]|50[0345])\.html$ {
try_files $uri /../default/$uri;
}
}
1. The 307 redirect preserves POST data while changing to GET method
2. Named locations (@notfound, etc.) provide clean error handling
3. The separate location block handles direct requests to error pages
4. Relative paths (/../default/) make the configuration more portable
For those who prefer not to use redirects:
map $request_method $error_page_method {
default "GET";
POST "GET";
}
server {
# ... other config ...
location @notfound {
limit_except GET { deny all; }
try_files /404.html /../default/404.html =404;
}
}
This approach uses Nginx's map directive to force GET method for error pages.
When configuring nginx to serve custom error pages, we often want a hierarchical fallback system where server-specific error pages take precedence over default ones. While this works perfectly for static files like robots.txt, implementing the same behavior for error pages presents unique challenges, particularly with different HTTP request methods.
For standard GET requests, this configuration works well:
server {
root /var/www/someserver.com;
error_page 404 = @notfound;
error_page 500 502 504 = @server_error;
location @notfound {
try_files /404.html /../default/404.html =404;
}
location @server_error {
try_files /500.html /../default/500.html =500;
}
}
The critical limitation appears when handling POST requests that result in errors. Nginx forwards the original request method when processing the error page, which leads to 405 Not Allowed errors when trying to serve static HTML files via POST.
Here's an improved version that properly handles all request methods:
server {
root /var/www/someserver.com;
# Primary error page configuration
error_page 404 /404.html;
error_page 500 502 503 504 /500.html;
# Fallback logic for error pages
location = /404.html {
internal;
try_files /404.html /../default/404.html;
}
location = /500.html {
internal;
try_files /500.html /../default/500.html;
}
# Special handling for maintenance mode
location = /503.html {
internal;
try_files /503.html /../default/503.html;
}
}
- The
internal
directive prevents direct external access to error pages - Relative paths using
../default/
work well when included in multiple server blocks - Each error page has its own dedicated location block for precise control
Always verify your configuration with both GET and POST requests:
# Test GET request to non-existent URL
curl -I http://yourserver.com/nonexistent-page
# Test POST request that will generate error
curl -X POST http://yourserver.com/nonexistent-api-endpoint
For high-traffic sites, consider these optimizations:
location ~* \.(html)$ {
expires 1h;
add_header Cache-Control "public";
etag off;
}