When working with HAProxy in reverse proxy configurations, there's often a need to pass the original request URL to backend servers. Many applications rely on the complete absolute URL for proper functionality, but HAProxy 1.4's limited variable support makes this challenging.
The key issue stems from HAProxy 1.4's inability to directly combine multiple request elements:
# This only captures the path+query portion
reqrep ^([^\\ ]*)\\ ([^\\ ]*)\\ (HTTP\\/1\\.[10]) \\0\\r\\nx-custom-header:\\ \\2
We need both the Host
header and request line components to reconstruct the full URL.
Here's a complete configuration that solves this problem:
frontend http-in
bind *:80
acl host_example hdr(host) -i example.com
use_backend example_backend if host_example
backend example_backend
# First capture the path+query
reqrep ^([^\\ ]*)\\ ([^\\ ]*)\\ (HTTP\\/1\\.[10]) \\0\\r\\nx-custom-path:\\ \\2
# Then combine with Host header using reqirep
reqirep ^([^\\ ]*)\\ ([^\\ ]*)\\ (HTTP\\/1\\.[10]) \\0\\r\\nx-original-url:\\ http://%[req.hdr(host)]%[req.hdr(x-custom-path)]
# Clean up temporary header
reqidel ^x-custom-path:.*$
server backend1 192.168.1.10:8080
server backend2 192.168.1.11:8080
server backend3 192.168.1.12:8080
The solution uses a two-step approach:
- First stores the path+query portion in a temporary header
- Then combines it with the Host header to form the complete URL
- Finally removes the temporary header
After applying this configuration, test with:
curl -v http://example.com/path/to/resource?param=value
The backend will receive:
GET /path/to/resource?param=value HTTP/1.1
Host: backend1:8080
x-original-url: http://example.com/path/to/resource?param=value
For high-traffic environments:
- Consider upgrading to HAProxy 1.5+ for better variable support
- Monitor performance impact of header manipulations
- Ensure proper header sanitization to prevent header injection
When working with HAProxy 1.4, I recently encountered a scenario where I needed to pass the complete original URL (including protocol, host, and path) to backend servers through a custom header. The standard reqadd
directive only allows static values, which doesn't solve our dynamic requirement.
The closest I got was using reqrep
to capture the path portion:
reqrep ^([^\\ ]*)\\ ([^\\ ]*)\\ (HTTP\\/1\\.[10]) \\0\\r\\nx-custom-header:\\ \\2
This produces x-custom-header: /foo?bar=baz
- missing the protocol and host information that we need for complete URL reconstruction.
After extensive testing, here's the working solution that combines both components:
frontend http-in
bind *:80
reqadd X-Original-URL:\ http://%[req.hdr(Host)]%[capture.req.uri]
default_backend servers
backend servers
server backend1 192.168.1.10:8080
server backend2 192.168.1.11:8080
server backend3 192.168.1.12:8080
The magic happens in the reqadd
line where we:
- Use
%[req.hdr(Host)]
to fetch the Host header value - Combine it with
%[capture.req.uri]
which captures the complete request URI (path + query) - Prepend
http://
since we know this is HTTP traffic (adjust for HTTPS as needed)
For more robust handling, consider these additional configurations:
# Force Host header if missing
reqidel ^Host:.*
reqadd Host:\ example.com
# Handle HTTPS detection
acl is_ssl req_ssl_ver gt 0
reqadd X-Original-URL:\ https://%[req.hdr(Host)]%[capture.req.uri] if is_ssl
reqadd X-Original-URL:\ http://%[req.hdr(Host)]%[capture.req.uri] if !is_ssl
To test your configuration:
curl -v http://example.com/test?param=value
# Should show:
# > GET /test?param=value HTTP/1.1
# > Host: example.com
#
# Backend should receive:
# X-Original-URL: http://example.com/test?param=value