HAProxy URL rewrite question/issue …

HAProxy version: v2.6.6

URL Frontend: http://haproxyserver.domain.com/application1
URL Backend: https://appserver1.otherdomain.com/

What I’m trying to achieve is that when a user visits http://haproxyserver.domain.com/application1, HAProxy gets the page at https://appserver1.otherdomain.com/ and send it back as http://haproxyserver.domain.com/application1

So, I want to rewrite “/application1” from the front to “/” in the back and send the response back as “/application1”. E.g. an image on the backend like https://appserver1.otherdomain.com/images/test.jpg should be send back as http://haproxyserver.domain.com/application1/images/test.jpg and so on.

My haproxy.cfg file without any rewrite config in it:

#---------------------------------------------------------------------
# PRD http frontend which proxys to the backends
#---------------------------------------------------------------------
frontend  PRD-http-front
    bind *:80
    http-request add-header X-Forwarded-For %[src]
    http-request add-header X-Forwarded-Proto http

    mode http
    option httplog
    option forwardfor       except 127.0.0.0/8
    maxconn 10240

    # define acl's
    acl host_haproxy-stats hdr(host) -i haproxy-stats.domain.com

    ###################################
    ### decide which backend to use ###
    ###################################
    # HAProxy statistics page
    use_backend haproxy-stats if host_haproxy-stats

    # Other
    use_backend application1 if { path_beg /application1 }

    # default backend
    default_backend PRD-local-httpd

#---------------------------------------------------------------------
# haproxy-stats backend
#---------------------------------------------------------------------
backend haproxy-stats
    mode http
    option httpclose
    option forwardfor
    http-request add-header X-Forwarded-For %[src]
    http-request add-header X-Forwarded-Proto http
    server haproxy-stats 127.0.0.1:8888 check

#---------------------------------------------------------------------
# PRD-local-httpd backend
#---------------------------------------------------------------------
backend PRD-local-httpd
    mode http
    option httpclose
    option forwardfor
    http-request add-header X-Forwarded-For %[src]
    http-request add-header X-Forwarded-Proto http
    server HAProxy-Server haproxyserver.domain.com:8081 check

#---------------------------------------------------------------------
# application1 backend
#---------------------------------------------------------------------
backend application1
    mode http
    option httpclose
    option forwardfor
    http-request add-header X-Forwarded-For %[src]
    http-request add-header X-Forwarded-Proto http
    server appserver1 appserver1.otherdomain.com:443 check ssl verify none

This config doesn’t work because HAProxy will try to load https://appserver1.otherdomain.com/application1 in the backend, which doesn’t exist. I Already tried several rewrite settings in the backend config part without any success. Some examples:

http-request replace-path ^([^\ ])\ /application1/(.) \1\ /\2
http-request replace-path /application1(.*) / \1
http-request set-path %[path,regsub(^/application1/?,/)]

I Even tried this in the frontend:

acl app1 path_beg -i /application1/
http-request set-path  /%[path] if app1

I Googled for hours and tried many things but I can’t figure it out. Every help is much appreciated!

It sounds like you need to modify responses to add back the path when HAProxy responds on behalf of the backend server. This is typically done in the Location header.

A word of caution: Some apps will let you play with this. Others send file payloads that include a relative path as the app expects it, so modifying the location header causes a mismatch and breaks things.

Assuming your app allows you to play with the Location header, I think you’re looking for something like this in your backend:

backend application1
    http-request replace-path /application1(.*) / \1
    http-response set-header Location /application1%[path]

That’s just an example. You may need to play around with the values a bit to get it to work.

Thank you very much for your reply and effort.
Unfortunately, the config gives an error when validating the config file:

error detected in backend ‘application1’ while parsing ‘http-response set-header’ rule : sample fetch <path]> may not be reliably used here because it needs ‘HTTP request headers’ which is not available here.

:wink:

This passes configuration checks on my instance of HAProxy. I’m not set up for what you’re trying to do, so I’m not able to test it for actual functionality.

backend application1
    # Removes /application1 before sending it to backend.
    http-request replace-path /application1(.*) \1
    # Adds /application1 to location on response from backend.
    http-response replace-header Location (.*) /application1/\1

Once again, there’s a good chance your application won’t like a proxy doing this as it messes with relative paths. I pulled something like this off for a little while by removing /admin from the pi-hole web interface, but an update broke it. I ended up having to let the app set its own paths. If your application supports setting its own paths, that’s the best way to accomplish this.

With this settings the config validates but just gives “Bad Request”.
Like you said: I’ll experiment some more with the values to see if I can get it to work.
I’m well aware that it might never work if paths are not all relative and even then it still might not work. I Will also contact the app vendor to see if they can help by changing their web config to serve from a subfolder.

Thank you very much once again for your time and effort! Much appreciated!

1 Like

hi,

did you get it working ? I have the exact same issue and it is working on the old setup:

  • Global frontend
...
acl app1 url_beg eq /app1

...
use_backend appint if app1
  • backend from app server (VM with Nginx … classical way) , which contains several apps
    balance leastconn
    log global
    mode http
    option forwardfor
    stick-table type ip size 1m expire 2m
    acl app1 url_beg eq /app1
    acl app2 url_beg eq /app2
    acl valid_method method GET POST HEAD
    acl response-is-redirect res.hdr(Location) -m found
    http-request set-header Host fra-test-app1.test.local if app1
    http-request set-header Host fra-test-app2.test.local if app2
    http-request replace-uri ^/app1/(.*) /\1 if app1 valid_method
    http-request replace-uri ^/app2/(.*) /\1 if app2 valid_method
    http-response replace-header ^Location (http|https)://fra-test-app1.test.local\/(.*)   Location:\ \1://%[hdr(host)]/app1/\2  if response-is-redirect
    http-response replace-header ^Location (http|https)://fra-test-app2.test.local\/(.*)   Location:\ \1://%[hdr(host)]/app2/\2  if response-is-redirect

This setup works and I get the /app1/… or /app2/… on the global frontend. The difference on the new setup is, that App server is not a classical Nginx host, but Docker (Nginx → PHP-fpm).

If I try the same ruleset, then the /app1 is removed before it reaches the Docker container, but not readded before the global frontend. So images / css and co failing because of the missing “/app1”

I tried also very long … and I don’t understand … what the issue is … the only thing I can imagine is, the Hostname from the Nginx in Docker …

ISSUE FOUND

After a looooot of debug … I’ve found the issue: it was never working that way, we have on the classical setup … these rules where not used at all, except http-request replace-uri ^/app1/(.*) /\1 if app1 valid_method. The backend apps are Symfony and they have a param called virtual_directory whtih was set on the VMs to app1/, but that wasn’t the case on the Docker image. After setting it there too … it started working :slight_smile:

Hours wasted …