How to Add X-Original-URL Header with Full Absolute Path in HAProxy 1.4 Configuration


4 views

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:

  1. First stores the path+query portion in a temporary header
  2. Then combines it with the Host header to form the complete URL
  3. 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