Error Interception & Custom Pages
Relevant source files
The following files were used as context for generating this wiki page:
Error Interception and Custom Pages in kamal-proxy provide a mechanism to replace standard HTTP error responses with branded or customized HTML templates. This system handles two primary types of errors: those generated by the proxy itself (e.g., target timeouts or unreachable backends) and those produced by the upstream application that the operator has chosen to intercept.
The system ensures that users see a consistent, professional interface even when the backend application is degraded or unavailable. It utilizes Go's html/template package to render embedded or filesystem-based HTML pages, supporting dynamic content such as custom error messages.
Sources: internal/server/error_intercept_middleware.go:12-16, internal/server/error_page_middleware.go:42-50
Architecture and Components
The error handling system is built using a layered middleware approach. It consists of the Intercept Middleware, which identifies errors from the backend, and the Page Middleware, which performs the actual rendering of HTML templates.
Data Flow for Error Handling
The following diagram illustrates how a request flows through the error middlewares when a backend failure occurs.
The ErrorPageMiddleware wraps the request context with an errorResponse object. If a downstream handler (like the intercept middleware) sets a status code in this context, the page middleware halts normal processing and renders a template.
Sources: internal/server/error_page_middleware.go:54-66, internal/server/error_intercept_middleware.go:34-40
Error Intercept Middleware
The ErrorInterceptMiddleware is responsible for monitoring responses from the target backend. If the backend returns a status code that matches a pre-configured list of "interceptable" errors, the middleware discards the original body and triggers the proxy's custom error rendering.
Intercept Logic
- Status Selection: Only 4xx and 5xx status codes are valid for interception.
- Header Cleaning: When an error is intercepted, entity headers like
Content-LengthandContent-Encodingare deleted to prevent conflicts with the proxy-generated error page. - Body Discarding: The original backend error body is discarded. The middleware reports writes as successful to the upstream
ReverseProxyto prevent panics, even though the data is not sent to the client.
Sources: internal/server/error_intercept_middleware.go:19-22, internal/server/error_intercept_middleware.go:61-75, internal/server/error_intercept_middleware_test.go:136-145
Key Data Structures
| Structure | Purpose |
|---|---|
ErrorInterceptMiddleware | Main handler containing the list of statuses to intercept. |
interceptingResponseWriter | A wrapper for http.ResponseWriter that captures the status code and suppresses the backend body if intercepted. |
| Sources: internal/server/error_intercept_middleware.go:17-26, internal/server/error_intercept_middleware.go:44-49 |
Error Page Middleware
The ErrorPageMiddleware manages the loading and rendering of HTML templates. It can be configured as a "root" middleware (providing default pages) or nested within services to provide service-specific error branding.
Template Loading and Resolution
Templates are loaded from a fs.FS filesystem. The middleware searches for files named [status_code].html (e.g., 404.html, 503.html).
If a specific template is not found, a root middleware provides a basic HTML fallback, while a non-root middleware allows the error to propagate to a parent handler.
Sources: internal/server/error_page_middleware.go:88-100, internal/server/error_page_middleware.go:111-120
Template Arguments
The middleware supports passing custom data to templates via TemplateArguments. For example, a 503 error can display a specific maintenance message if provided.
Sources: internal/server/error_page_middleware.go:18-21, internal/pages/503.html:200-205
Default Error Pages
kamal-proxy includes a set of embedded default error pages. These are used when no custom ErrorPagePath is provided by the operator.
| Status | Template Path | Description |
|---|---|---|
| 404 | internal/pages/404.html | Page Not Found |
| 413 | internal/pages/413.html | Content Too Large |
| 502 | internal/pages/502.html | Bad Gateway |
| 503 | internal/pages/503.html | Service Temporarily Unavailable |
| Sources: internal/pages/embed.go:6, internal/pages/404.html, internal/pages/413.html, internal/pages/502.html, internal/pages/503.html |
Implementation Details
Setting an Error Response
The SetErrorResponse function is the primary API for handlers to trigger a custom error page. It updates the request context with the desired status code.
func SetErrorResponse(w http.ResponseWriter, r *http.Request, statusCode int, templateArguments any) {
errorResp, ok := r.Context().Value(contextKeyErrorResponse).(*errorResponse)
if ok {
errorResp.StatusCode = statusCode
errorResp.TemplateArguments = templateArguments
} else {
// Fallback in case no middleware is present in the chain
http.Error(w, http.StatusText(statusCode), statusCode)
}
}
Sources: internal/server/error_page_middleware.go:28-37
Middleware Nesting
Testing confirms that the middleware supports nesting. A service-specific ErrorPageMiddleware can handle a subset of errors (e.g., a custom 404), while falling back to a global middleware for others (e.g., a generic 503).
Sources: internal/server/error_page_middleware_test.go:64-75
Summary
The Error Interception & Custom Pages system in kamal-proxy ensures resilience and brand consistency. By intercepting backend errors and utilizing a flexible template rendering engine, the proxy provides clear feedback to users even during total backend failure. The use of Go's embed package guarantees that basic error pages are always available without external dependencies.
Sources: internal/server/error_intercept_middleware.go:12-16, internal/pages/embed.go:6