Advanced Nginx Reverse Proxy Error Handling: Custom Pages and Selective 400 Status Interception


2 views

When implementing Nginx as a reverse proxy, error handling becomes more complex than in standalone configurations. The primary challenge emerges from needing to:

  • Maintain backend-originated JSON error details for 400 Bad Request responses
  • Consistently serve custom error pages for all status codes
  • Avoid default Nginx error pages under any circumstances

Here's a robust solution that addresses both requirements:

http {
    # Define common error page path
    proxy_intercept_errors on;
    
    # Handle standard error codes
    error_page 401 /error_pages/401.html;
    error_page 403 /error_pages/403.html;
    error_page 404 /error_pages/404.html;
    error_page 500 502 503 504 /error_pages/50x.html;
    
    server {
        listen 80;
        server_name example.com;
        
        location / {
            proxy_pass http://backend;
            proxy_set_header Host $host;
            
            # Special handling for 400 responses
            proxy_intercept_errors off;
            proxy_next_upstream error timeout invalid_header http_400;
            
            # Return to default error handling for other codes
            error_page 400 = @fallback;
        }
        
        location @fallback {
            proxy_intercept_errors on;
            proxy_pass http://backend;
        }
        
        # Custom error page locations
        location /error_pages/ {
            internal;
            root /var/www/html;
        }
    }
}

The solution combines several Nginx features:

Status-Code-Specific Interception

Using proxy_next_upstream with http_400 allows passing through 400 responses while still intercepting other errors. The @fallback named location ensures proper handling of all other status codes.

Comprehensive Error Coverage

To prevent any default Nginx pages from appearing, we recommend:

  1. Creating error pages for all standard HTTP status codes
  2. Implementing a catch-all error handler for unexpected codes
# Catch-all error handler
error_page 300-599 /error_pages/generic.html;

Verify your configuration with these test cases:

  • Trigger 400 errors with malformed requests
  • Test standard error codes (404, 500, etc.)
  • Verify custom pages for obscure status codes (418, 429, etc.)

The additional error handling adds minimal overhead. For high-traffic sites, consider:

  • Caching error pages with appropriate headers
  • Using error_page directives in the server context rather than per-location
  • Keeping error page assets lightweight

When implementing Nginx as a reverse proxy, error handling becomes more complex than in standalone configurations. We need to maintain two key requirements simultaneously:

  1. Preserve backend-generated JSON error details for 400 Bad Request responses
  2. Consistently serve custom error pages for all other status codes

The solution involves combining several Nginx directives with conditional logic. Here's the approach:


http {
    # Default error page configuration
    proxy_intercept_errors on;
    
    # Custom error pages for common status codes
    error_page 401 /errors/401.html;
    error_page 403 /errors/403.html;
    error_page 404 /errors/404.html;
    error_page 500 502 503 504 /errors/50x.html;
    
    server {
        listen 80;
        server_name example.com;
        
        location / {
            proxy_pass http://backend;
            
            # Special handling for 400 errors
            proxy_intercept_errors off;
            proxy_next_upstream error timeout invalid_header http_400;
            
            # Error response validation
            proxy_set_header Accept application/json;
        }
        
        # Error page locations
        location /errors/ {
            internal;
            root /var/www/html;
        }
    }
}

The configuration works through several mechanisms:

  • proxy_intercept_errors is globally enabled but selectively disabled for 400 status handling
  • The proxy_next_upstream directive ensures proper handling of 400 errors
  • JSON content negotiation is enforced via proxy_set_header
  • All error pages are served from an internal location block

For complete coverage of all possible status codes, consider this pattern:


map $status $error_page {
    default             /errors/generic.html;
    ~^[4-5][0-9]{2}$   /errors/$status.html;
}

server {
    # ... other configuration ...
    
    error_page 400 = @no_intercept;
    location @no_intercept {
        proxy_pass http://backend;
        proxy_intercept_errors off;
    }
    
    location / {
        error_page 400 = @no_intercept;
        # ... other proxy settings ...
    }
}

The solution maintains efficiency by:

  • Using Nginx's native pattern matching for status codes
  • Minimizing conditional logic in the request path
  • Leveraging internal redirects for error page serving