I got a special request to configure a HAProxy load balancer to use a different backend when a certain string was found in the URL.
There are already some very good answers how to do that (for example http://serverfault.com/questions/127491/haproxy-forward-to-a-different-web-server-based-on-uri) but I had to adapt the config for my environment which includes a combination of already existing acl's.
To show the current architecture:
test.example.com -> backend1 -> internalserver1:8888
stage.example.com -> backend2 -> internalserver2:8888
Because the HAProxy listens on the same IP address, both domains test.example.com and stage.example.com are handled by the same HAProxy. But depending on which domain was called, the requests should be forwarded to a different backend.
To be able to handle different domains, an acl which checks the requested host address does the job:
frontend main_http
bind *:80
# Define ACL based on host names
acl test_host hdr(host) -i test.example.com
acl stage_host hdr(host) -i stage.example.com
# Default Route to normal backends
use_backend backend_test_8888 if test_host
use_backend backend_stage_8888 if stage_host
The ACL's "test_host" and "stage_host" check which host name was requested by using hdr(host). If it matches test.example.com, the acl "test_host" is set to true.
Depending which ACL is true (e.g. which host name was requested), a different backend is used (backend_test_8888 or backend_stage_8888).
Now I need an additional ACL which handles a special string (blog) in the requested URL. These requests should be sent to another backend.
test.example.com -> backend1 -> internalserver1:8888
test.example.com/bla/blog/page3 -> backend3 -> internalserver1:8889
stage.example.com -> backend2 -> internalserver2:8888
stage.example.com/bla/blog/page3 -> backend4 -> internalserver2:8889
In the serverfault.com example from above the answer was to use "path_beg" but that doesn't work for my case because I need to make sure that the string is not only at the beginning of the URI, it could appear literally anywhere in the whole URI.
To solve this, I created the following ACL which uses the sub(-string) rather than beg lookup method:
# Define ACL based on string in URI
acl blog_in_uri path_sub blog
So the ACL "blog_in_uri" is set to true, as soon as the string "blog" appears in the requested URI.
How to combine these two ACL's now? First I needed to create separate backends to handle the different destination ports:
#---------------------------------------------------------------------
# backend_test_8889
#---------------------------------------------------------------------
backend backend_test_8889
balance source
server intenralserver1 192.168.44.10:8889 check inter 2000 fall 1 rise 3
server intenralserver11 192.168.44.11:8889 check inter 2000 fall 1 rise 3
#---------------------------------------------------------------------
# backend_stage_8889
#---------------------------------------------------------------------
backend backend_stage_8889
balance source
server intenralserver2 192.168.44.20:8889 check inter 2000 fall 1 rise 3
server intenralserver22 192.168.44.22:8889 check inter 2000 fall 1 rise 3
Then I combined the ACL's in the "use_backend" syntax. Important is the following information from the HAProxy config documentation:
There may be as many "use_backend" rules as desired. All of these rules are evaluated in their declaration order, and the first one which matches will assign the backend.
This means that in my scenario I have to use the ACL combination (hostname + blog in uri) first. If it doesn't match, it will use the second ACL (hostname only).
To combine two or more ACL's just write both one after another. HAProxy will understand this as logical AND (do not add the word ADD into the config, this is considered a syntax error!):
frontend main_http
bind *:80
# Define ACL based on host names
acl test_host hdr(host) -i test.example.com
acl stage_host hdr(host) -i stage.example.com
# Define ACL based on string in URI
acl blog_in_uri path_sub blog
# Special route which matches both hostname and string acl
use_backend backend_test_8889 if test_host blog_in_uri
use_backend backend_stage_8889 if stage_host blog_in_uri
# Default Route to normal backends
use_backend backend_test_8888 if test_host
use_backend backend_stage_8888 if stage_host
Now all the requests which contain the string "blog" in the URI will be forwarded to a different backend rather than using the default backend for the requested domain.
Levente Peres from Demecser, Hungary wrote on Sep 30th, 2017:
Simple and elegant solution.
I had a very similar problem, and this helped me greatly.
Thank you!!
MikeJ from wrote on Jun 7th, 2017:
thanks, still totally relevant in 2017! solved my problem completely.
Rodolfo from Portugal wrote on Feb 7th, 2017:
Amazing Solution!
Thank you!
AWS Android Ansible Apache Apple Atlassian BSD Backup Bash Bluecoat CMS Chef Cloud Coding Consul Containers CouchDB DB DNS Database Databases Docker ELK Elasticsearch Filebeat FreeBSD Galera Git GlusterFS Grafana Graphics HAProxy HTML Hacks Hardware Icinga Influx Internet Java KVM Kibana Kodi Kubernetes LVM LXC Linux Logstash Mac Macintosh Mail MariaDB Minio MongoDB Monitoring Multimedia MySQL NFS Nagios Network Nginx OSSEC OTRS Office PGSQL PHP Perl Personal PostgreSQL Postgres PowerDNS Proxmox Proxy Python Rancher Rant Redis Roundcube SSL Samba Seafile Security Shell SmartOS Solaris Surveillance Systemd TLS Tomcat Ubuntu Unix VMWare VMware Varnish Virtualization Windows Wireless Wordpress Wyse ZFS Zoneminder