I discovered recently that you can match a backend dynamically, based on the request hostheader, like this:

 use_backend %[req.hdr(host),lower]

However, does anyone know any way I could use the subdomain of the request hostheader to match the backend?

E.g. something along these lines:

backend one
backend two

use_backend %[<SUBDOMAIN OF HOSTHEADER>,lower]

which would match like this:

 one.example.com -> backend one
 two.example.com -> backend two

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.

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.

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.


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.

  • Thanks, this looks great. However I'm getting an error on the use_backend directive: sample fetch <req.hdr(host),lower,regsub(^(.*)(?:\..*){2}$,\1)> failed with : invalid arg 2 in conv method 'regsub' : missing arguments (got 1/2), type 'string' expected - any ideas? – UpTheCreek Nov 25 '15 at 14:54
  • Yeah... I didn't actually test it. It was just based on theory. You might have to wrap the arguments in quotes: regsub('^(.*)(?:\..*){2}$','\1') – GregL Nov 25 '15 at 15:00
  • I already tried that (since it was complaining about string in error), but the error remains the same. I'll read more about this regsub converter. Thank you. – UpTheCreek Nov 25 '15 at 15:04
  • I think it might be this limitation (closing parenthesis) 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. Doesn't seem to be an obvious way around that :( – UpTheCreek Nov 25 '15 at 15:08
  • 2
    @UpTheCreek, Fixed the answer to reflect what you discovered. If it's to your liking, feel free to throw me a Check. – GregL Dec 2 '15 at 1:46

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.

  • Thanks. I want to try and keep it dynamic, to reduce the number of config changes necessary. I'll certainly consider this option though, if it turns out dynamic is not possible. – UpTheCreek Nov 23 '15 at 16:33
  • Be sure to use map_dom instead of map. That's because, according to RFC specification, the host header MAY contain port number, e.g. example.com:443 or example.com:8080. map_dom extracts only the domain portion for comparison. – Jonathan Oliver Apr 7 '16 at 16:32
  • One other caveat is that map_dom will search the file from top to bottom so ordering is important. If you have a match at the top "a.com" and a match at the bottom "b.a.com", and the domain comes in as "b.a.com" you would think it would choose the last. Instead, it chooses "a.com" which technically matches, although it's clearly not the desired or intended match. – Jonathan Oliver Sep 13 '16 at 17:38

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.