Setting headers dynamically on error documents is a limitation of HAProxy and there is currently no out-of-the-box feature available to achieve this directly. Since the error is processed before it even hits your backend servers, dynamically setting the Access-Control-Allow-Origin header is difficult.
A commonly suggested workaround for this use-case involves sending error responses through a dedicated backend. In this backend, you can execute scripts or use an application that can inspect the request and add headers dynamically.
Here are the steps in HAProxy configuration:
frontend www-https
bind *:80
bind *:443 ssl crt /etc/ssl/cert.io.pem
redirect scheme https code 301 if !{ ssl_fc }
mode http
use_backend errorBackend if { status eq 504 }
default_backend myBackend
backend errorBackend
mode http
errorfile 504 /etc/haproxy/errors/504.http
http-response set-header Access-Control-Allow-Origin %[hdr(origin)]
server errSrv localhost:2346
backend myBackend
server myServer 123.456.789.101:2345
When haproxy itself returns an error (like a 504 due to a timeout) the error flag is raised before getting to the backend, and it'll return the pre-configured error page directly, without passing the request to backend. Therefore, in this situation haproxy won't be able to process the headers which involve dynamic properties (e.g. varying on the requesting domain).
The workaround of sending errors to a dedicated backend makes haproxy behave like this:
- A client request comes to the frontend.
- Haproxy tries to forward the request to the backend.
- If the backend times out (which would usually trigger a 504 directly from haproxy), we instead direct the request to a dedicated 'error backend'.
- The 'error backend' creates the 504 message including headers based on the original request (as the 'error backend' works like the ordinary backend).
- This customized error message is then sent back to the client.
The trick is that we're not actually allowing haproxy to trigger a direct error response. The timeout will now result in switching to another backend which can process request-based dynamic headers. Hence, it does create another level of abstraction in the configuration, which can be considered as a new layer.