Haproxy - selecting backend dynamically from subdomain

Adding and removing DNS entries allows you to route subdomains to various backends on the fly, buy you still need to define those backends so there's still a service restart. As such, I'm not entirely sure of the usefulness of this config.

In any event, here's how you'd do it.

We know we can find the contents of the host header, by using req.hdr (req.hdr(host)), but that gives us the FQDN of the request, not the subdomain.

Thankfully, there's a regsub converter we should be able to apply to the req.hdr sample to clip off the base domain and TLD.

regsub(<regex>,<subst>[,<flags>])
Applies a regex-based substitution to the input string. It does the same
operation as the well-known "sed" utility with "s/<regex>/<subst>/". By
default it will replace in the input string the first occurrence of the
largest part matching the regular expression <regex> with the substitution
string <subst>. It is possible to replace all occurrences instead by adding
the flag "g" in the third argument <flags>. It is also possible to make the
regex case insensitive by adding the flag "i" in <flags>. Since <flags> is a
string, it is made up from the concatenation of all desired flags. Thus if
both "i" and "g" are desired, using "gi" or "ig" will have the same effect.
It is important to note that due to the current limitations of the
configuration parser, some characters such as closing parenthesis or comma
are not possible to use in the arguments.
The first use of this converter is
to replace certain characters or sequence of characters with other ones.

The emphasis in that quote is mine and aims to show that in this case, where the regex you'd need is ^(.*)(?:\..*){2}$, it won't work because of the parenthesis.

Thus, you'll need to use the field converter.

field(<index>,<delimiters>)
Extracts the substring at the given index considering given delimiters from
an input string. Indexes start at 1 and delimiters are a string formatted
list of chars.

field(1,'.')

If we put the whole sample pipline together, the use_backend line looks like:

use_backend BE:subs-%[req.hdr(host),lower,field(1,'.')]

Now, this opens up the fact that one.*.* will go to the same backend, and could lead to some very weird situations.

It might make some sense to check the base domain and TLD to ensure they're what you expect. Assuming you've only got two (example.com and foo.com) of them, you'd use req.hdr_end(host) to check for them, making the ACL look like:

 acl is_valid_base_domain  req.hdr_end(host) -i example.com foo.com

And if we put it all together, the whole config would look something like this:

frontend FE:subs
  ...
  acl is_valid_base_domain  req.hdr_end(host) -i example.com foo.com
  use_backend BE:subs-%[req.hdr(host),lower,field(1,'.')] if is_valid_base_domain

  default_backend BE:subs:default

backend BE:subs-one
  #matches one.example.com, one.foo.com
  ...

backend BE:subs-two
  #matches two.example.com, two.foo.com
  ...

backend BE:subs-three
  #matches three.example.com, three.foo.com
  ...

backend BE:subs:default
  #matches *.example.com, *.foo.com 
  ...

You can get even fancier if you want by having different "dynamic" backends for each sub-domain, per base domain; you'd just need to use the pieces above to work that out.


As far as I know, HAProxy doesn't have regex support to extract specific part of the subdomain from the Host header and then assign that value to a variable, which is later used to form complete backend name.

However, you one way to solve your problem would be to use mapping:

frontend frontend_main
...
use_backend %[req.hdr(host),lower,map(/etc/haproxy/subdomains.map,backend_main)]

The content of /etc/haproxy/subdomains.map would look like this:

#domainname  backendname
one.example.com backend_one
two.example.com backend_two
etc.domain1.com backend_etc

All requests that don't match any of the subdomains in that file will go to backend_main backend.