Set up custom error pages in HAProxy to ensure consistent, branded messaging that supports any backend web stack.
The memory is probably still fresh: You’re shopping online at your favorite website, looking for something specific, you’ve got it narrowed down to two or maybe three products, you make the final decision, click to checkout and then—
Internal Server Error. A cryptic error has replaced the page you were expecting. More than surprised, you feel knocked off balance. Where do you go from here?
When a customer comes across an error, there can be a number of causes: the page doesn’t exist, there was a network-related error, or today was the day that a bug buried deep inside the code decided to manifest itself. Whatever the cause, the customer is now in a state of dismay and it’s imperative that you guide them back to the main site and, if possible, restore their faith in your company. One way to do that is by showing a better error page.
There are plenty of examples of customized error pages that aim to delight and entertain users who are unfortunate enough to come across them. For inspiration check this list by Designmodo and this one from Canva. By customizing your error pages, you can keep the same tone as the rest of your website, using on-brand colors, images, and voice. The big challenge is finding a proven way to serve custom error pages, one that works with any web server technology or mix of technologies.
As we’ll cover here, you can guarantee the consistent and reliable delivery of custom error pages by storing them in your HAProxy load balancer, which sits in front of your web servers. HAProxy relays requests and responses between clients and servers and if it detects an error it will replace the bleak, unbranded error page with the one you’ve created. By delegating this task to your load balancer, you guarantee a consistent delivery of these pages, even if the backend servers have crashed and are no longer reachable.
HAProxy version 2.2 expanded support for custom error pages by introducing dynamic error handling with the new
http-errors section, which makes it easy to assign different error pages to different websites and to create error pages that return different data formats, such as JSON. It also added functionality to intercept and return a response without contacting backend servers at all, which is ideal for serving maintenance pages and other types of status pages.
Add Custom Error Pages to HAProxy
To add a custom error page, start by creating a new folder under /etc/haproxy such as /etc/haproxy/errors that will hold your error page files. Next, define your error pages. They should have the .http file extension, since they include both HTML markup and the HTTP status code and response headers. Here is an example 404 error page file, which you could store at /etc/haproxy/errors/404.http:
Next, open the HAProxy configuration file, /etc/haproxy/haproxy.cfg, and add a section called
http-errors that contains an
errorfile directive that points to the 404.http file. This goes at the same level as a
You can create more .http files to handle other statuses, such as 500 Server Error and 403 Forbidden, and then add lines to the
http-errors section for them. HAProxy supports any of the following status codes: 200, 400, 401, 403, 404, 405, 407, 408, 410, 425, 429, 500, 502, 503, and 504.
By default, HAProxy will serve these files only when it triggers the error itself. For example, if HAProxy can’t reach any of your backend servers it will trigger a 503 Service Unavailable error. Or, if it successfully reaches a server, but then exceeds its timeout while waiting for a reply, it will return a 504 Gateway Timeout error. You can configure it to return your custom error files for these types of errors by including an
errorfiles directive in your
To replace other errors, such as 404 Not Found and 500 Server Error, you’ll need to check which status code the server returned and then have HAProxy replace the response using that same code. Consider this
frontend section, which overwrites the standard 404 error page returned by the server:
http-response return line intercepts responses that have a status of 404 and returns a custom error page from the myerrors section. Its
default-errorfiles parameter tells it to use the files referenced by the
Here’s the result when accessing a page that doesn’t exist:
Per-Site Custom Error Pages
Now that you’ve seen how to return custom error pages, we’ll take a look at other ways to fine-tune them. By placing an
errorfiles directive into a
frontend, as shown in the previous section, you are indicating which
http-errors section to use. However, some people proxy more than one website through the same frontend and then route requests to different backends depending on the host header received. In that case, you could use conditional statements to select a different
http-errors section dynamically depending on the website the user is accessing.
In the next example, one set of error pages is returned if the website is site1.com and a different one is returned for site2.com. This works by checking the incoming host header, which shows the name of the website, and then executing the
http-response return line that matches that name by using an if statement.
Note that you need to store the host header in a variable by using the
http-request set-var directive, since HAProxy won’t have access to it during the response phase otherwise. HAProxy is capable of proxying many websites through the same frontend and then choosing the appropriate backend based on conditional logic. With this technique, you’re able to match error pages with the requested site.
Rendering Dynamic Content
Going beyond returning a static HTML page, you may also find it helpful to include extra details that the customer could screenshot and send back to you. For example, you could record a unique ID in your HAProxy logs for each request and then display that ID on the error page so that it’s easier to find the corresponding log line later. You can also show other information, such as the HTTP request headers, client’s IP address, cookie values, and so forth.
unique-id-format directive creates a unique identifier for each request using the given format. In this example, the unique ID consists of the client’s IP address and port, the frontend’s IP address and port, the state indicating how the request was terminated, the request counter, and the HAProxy process ID, all encoded as hexadecimal.
A request’s unique ID is forwarded to the backend server as an HTTP header by including the
unique-id-header directive with the name you want to give to the header, such as X-Unique-ID. It’s also included in the logs by appending the
unique-id fetch method to the end of a custom log format, which is set with
log-format. Last, we show it to the customer on the error page by including it in the 404.html file. Note that we’re using the
lf-file parameter on the
http-response return line so that the HTML file becomes a template that can render HAProxy variables and fetch methods. Here are the contents of the 404 HTML file:
Here is the result:
http-response return with the
lf-file parameter works well for replacing server errors with templated HTML files, it won’t capture errors generated from HAProxy itself. Typically, you would use
errorfiles to specify static error pages to use for those. However, in version 2.2, you can use the
http-error status directive to set template files for HAProxy-generated errors too, using its
lf-file parameter. Its syntax is nearly identical to
http-response return, except that it does not allow an if statement to follow it.
Return JSON errors
For services that normally return JSON-formatted responses, you’ll want to create custom error pages that return JSON instead of HTML. For instance, you could add the following file as /etc/haproxy/errors/503-json.http:
Then add a new
http-errors section to your HAProxy configuration and reference it in your service’s
frontend section. You can use the
errorfiles directive to intercept only errors that HAProxy emits or use
http-response return to override other errors from the servers:
The JSON-formatted 503 Service Unavailable response will be returned for this service when all backend servers are down.
HAProxy version 2.2 added another helpful feature: the ability to return responses without contacting the backend server at all. The new native response generator introduces the
http-request return directive, which returns content directly from HAProxy. There’s quite a bit you can do with this, even building up small services such as the one Daniel Corbett created here, for which the configuration is here, which hashes a string using various hashing algorithms. Its hashing functionality comes from HAProxy’s built-in converters. The page you return can display properties that HAProxy captures, giving you access to HAProxy’s fetch methods and converters.
http-request return to create a simple maintenance page that tells visitors that the site is currently undergoing some scheduled work. First, add the file /etc/haproxy/maintenance.html with the following markup:
Then, update your haproxy.cfg file to return this file by using the
http-request return directive:
Now, your website will display the maintenance page. Of course, you’ll want to style it better using your own company branding.
Interested in advanced security and administrative features? HAProxy Enterprise is the world’s fastest and most widely used software load balancer. It powers modern application delivery at any scale and in any environment, providing the utmost performance, observability, and security. Organizations harness its cutting edge features and enterprise suite of add-ons, backed by authoritative expert support and professional services. Ready to learn more? Sign up for a free trial.