nginx error_page 400 Not Working: How to Fix Custom Error Page Ignored Issue


2 views

When configuring custom error pages in nginx, developers often encounter situations where the error_page directive appears to be silently ignored. The specific case we're examining involves returning a custom 400 error page for non-HTTPS requests, where despite correct configuration, the default nginx error page persists.

The initial configuration attempts show common patterns that don't work due to nginx's internal processing order:

server {
    listen 80;
    error_page 400 /400.html;
    location = /400.html {
        root /var/www/html;
    }
    return 400;
}

The issue stems from nginx processing the return directive differently than expected. When using return with a status code, nginx immediately terminates request processing and returns the specified status code without considering error_page directives for that code.

Solution 1: Using rewrite with error_page

server {
    listen 80;
    root /var/www/html;
    
    error_page 400 /400.html;
    location = /400.html {
        internal;
    }
    
    if ($scheme != "https") {
        rewrite ^ /400.html;
        return 400;
    }
}

Solution 2: Using try_files approach

server {
    listen 80;
    root /var/www/html;
    
    error_page 400 /400.html;
    location / {
        try_files /nonexistentfile @validate;
    }
    
    location @validate {
        return 400;
    }
}

For custom error pages to work with HTTP 400 status codes:

  • The error page must be accessible through a location block marked as internal
  • Don't mix immediate return with error_page for the same status code
  • Consider using conditional logic (if) for HTTP/HTTPS validation
  • Ensure proper filesystem permissions for the error page

After implementing any solution, verify with:

curl -I http://yourserver

You should see both the 400 status code and your custom page content.


When implementing custom error pages in nginx, many developers encounter situations where the error_page directive appears to be silently ignored, particularly with HTTP 400 errors. Here's why your configuration might not be working as expected:

server {
    listen 80;
    error_page 400 /400.html;
    location = /400.html {
        root /var/www/html;
    }
    return 400;
}

The issue presents several puzzling behaviors:

  • The return 400 statement works (changing the code produces different default nginx pages)
  • The custom 400.html file exists and serves correctly when accessed directly
  • Yet the custom page never appears when triggered by the return statement

The root cause lies in nginx's request processing phases. The return directive in the server context immediately terminates processing, bypassing the error_page handler. This differs from errors that occur during request processing (like invalid requests or upstream errors).

Here are three proven approaches to make custom 400 pages work:

Method 1: Trigger via Invalid Request

server {
    listen 80;
    server_name example.com;
    root /var/www/html;
    
    error_page 400 /400.html;
    location = /400.html {
        internal;
    }

    # Force 400 by requiring HTTPS
    if ($scheme != "https") {
        return 400;
    }
}

Method 2: Use location-based Return

server {
    listen 80;
    root /var/www/html;
    
    error_page 400 /400.html;
    location / {
        return 400;
    }
    
    location = /400.html {
        internal;
    }
}

Method 3: Rewrite + Error Page

server {
    listen 80;
    root /var/www/html;
    
    error_page 400 /400.html;
    
    location / {
        rewrite ^ /invalid-request;
    }
    
    location = /400.html {
        internal;
    }
}
  • Always mark error page locations as internal
  • Place error pages outside protected locations
  • Test with curl -v http://yourserver to verify headers
  • Check nginx error logs (/var/log/nginx/error.log) for troubleshooting

For more sophisticated error handling, consider using named locations:

server {
    listen 80;
    root /var/www/html;
    
    error_page 400 @custom400;
    
    location @custom400 {
        root /var/www/errors;
        try_files /400.html =400;
    }
    
    location / {
        return 400;
    }
}