How to Map Domain Names to Backend Server Pools With HAProxy

Your HAProxy load balancer may only ever need to relay traffic for a single domain name, but HAProxy can handle two, ten, or even ten million routing rules without breaking a sweat. This article shows several ways of handling multi-domain configurations, including an introduction to using HAProxy maps. There’s a range of techniques explained here, whether you are deploying a simple, fairly-static multi-domain web server, leveraging HAProxy’s more advanced features to tame the configuration and management of a dynamic API Gateway, or anything in between.

Access Control List Mapping

If your HAProxy is already serving multiple domains, you’re probably familiar with using Access Control Lists (ACLs) in your frontend declaration. This works well, but once you find yourself writing ACLs for many domains, it can become difficult to maintain.

Here’s how you might write ACLs for two domains, example.com and example.net:

frontend default
bind :80
# ACL for "example.com" and "www.example.com"
acl ACL_example.com hdr(host) -i example.com www.example.com
use_backend be_example.com if ACL_example.com
# ACL for "example.net"
acl ACL_example.net hdr(host) -i example.net
use_backend be_example.net if ACL_example.net

Let’s take the second ACL as an example. It breaks down as follows:

acl

ACL definition begins with acl.

ACL_example.net

The name of the ACL. The name is arbitrary, but it’s good form to make them understandable.

hdr(host) -i example.net www.example.net

This says that this rule will match any request that has an HTTP Host header of

example.net or www.example.net.

Once the ACL is set, define what to do with it:

use_backend be_example_net if ACL_example.net

This tells HAProxy to send any matching requests to a backend named be_example_net.
This is a good approach for a small number of domains if they are fairly static, but what happens when you need it to handle tens of thousands of domains mapped to multiple backends, and you need to change them dynamically?

Direct Mapping

One strategy is to simply create a backend with the same name as your incoming domain names and use this use_backend directive in your frontend:

use_backend be_example_net if ACL_example.net
frontend fe_main
bind :80
# If Host header is api.example.com then use
# api.example.com backend
use_backend %[req.hdr(Host),lower]

Above, %[req.hdr(host)] is replaced with the incoming host header, and forced to lowercase with lower. Therefore, if a request comes in for api.example.com, it will be sent to this backend:

backend api.example.com
balance roundrobin
server api1 127.0.0.1:8080 check
server api2 127.0.0.1:8081 check

Please note that the incoming host header variable includes any port explicitly specified, so incoming requests for example.com:6666 would be sent to a backend named backend example.com:6666, which may or may not exist. To strip the port, use:
use_backend %[req.hdr(host),lower,word(1,:)]

HAProxy Maps

If you need something more flexible and dynamic than ACLs or Direct Mapping, take a look at HAProxy maps. A map is an in-memory key/value data structure of a type known as an Elastic Binary Tree that is loaded at startup from a text file that you specify in your HAProxy configuration file. These maps are highly-optimized search tree structures that allow for incredibly fast lookups of the data stored within. The format of the text file used to create the map is quite simple: two columns, separated by one or more spaces or tabs. Comment lines begin with a hash (#) and must be on their own line.

#domainname backendname
example.com be_default
example.net be_default
api.example.com be_api
api1.example.net be_api1
api2.example.com be_api2
# [...]
api10000.example.com be_api

To have HAProxy load this file, save it as /etc/haproxy/maps/hosts.map and then add the following line to your frontend config:

frontend default
bind :80
use_backend %[req.hdr(host),lower,map_dom(/etc/haproxy/maps/hosts.map,be_default)]

When using an ACL for this task, there was a line that looked like this:

use_backend be_example.com if ACL_example.com

But when using a map, the use_backend line gets a little more complicated, so let’s break it down. The directive use_backend is the same, but the second part within the square brackets is as follows:

req.hdr(host) is the Host header that contains the domain part of the URL. This is the key that we look up in the map.
Using lower forces the Host header value to lowercase, turning “EXAMPLE.COM” into “example.com” to simplify matches.
map_dom(/path/to/map,be_default)

The map_dom function takes two arguments, the first being the location of the map and the second being the default backend to use if an incoming Host header isn’t in the map file.

In addition to mapping domain names to backends, you can also use a similar technique to map URL paths to backends. This simplifies some of the complexities of designing and maintaining the type of routing explained in Using HAProxy as an API Gateway, Part 1 [Introduction].
To map paths to backends using a map, create a use_backend line that uses the path fetch method to get the URL path and the map_beg converter to find that path in the map file.

frontend www
bind :80
use_backend %[path,map_beg(/etc/haproxy/maps/routes.map,be_default)]

Your route map, located at /etc/haproxy/maps/routes.map in the line above, might look like this:

/api be_api
/login be_auth

Routes that aren’t listed in the map are sent to the default backend, as well as requests for routes in the map that point to nonexistent or unavailable backends.

Modifying Maps

You have several options for modifying your maps, depending on your use case.

Manual update

The simplest method is to edit the map file in a text editor and do a hitless reload of HAProxy. This is a direct way of making a change, but it’s a manual process. Using hitless reloads means that you won’t lose any connections while it is reloading.

HAProxy Runtime API

You can directly interact with a running instance of HAProxy by using HAProxy’s Runtime API. To do this, first ensure that a socket has been defined in the global section of your configuration file:

stats socket /var/run/haproxy.sock
user haproxy group haproxy mode 660 level admin expose-fd listeners

Test your socket using socat by piping echo “help” at it:

$ echo "help" | sudo socat stdio /var/run/haproxy.sock | grep map

This will produce the following output, which shows you several map functions:

add map : add map entry
clear map <id> : clear the content of this map
del map : delete map entry
get map : report the keys and values matching a sample for a map
set map : modify map entry
show map [id] : report available maps or dump a map's contents

For example, to add a mapping for a domain and backend pairing, use:

$ echo "add map /etc/haproxy/maps/hosts.map api.example.com be_api" | socat stdio /var/run/haproxy.sock

Deleting a map entry with the Runtime API is just as straightforward:

$ echo "del map /etc/hapee-2.1/hosts.map api.example.com" | socat stdio /var/run/haproxy.sock
Note

Changes to the running instance made using this API are not written to disk and will be lost if you do a restart or reload of HAProxy.

HAProxy Data Plane API

Maps can also be managed using a RESTful interface, using the HAProxy Data Plane API. Unlike the Runtime API, with the Data Plane API, changes are written to disk.
Automatic Dynamic Updates via URL
HAProxy Enterprise can set maps dynamically via URL, checking for changes at an interval you specify:

dynamic-update
update id /etc/hapee-2.1/maps/domains.map url http://10.0.0.1/domains.map delay 300s

This is ideal if you run a cluster of HAProxy Enterprise instances since their map files can all be kept in sync with the file you host at the configured URL.

Conclusion

HAProxy is the ideal tool for load balancing multiple domains, whether the number of domains is ten or ten thousand. ACLs are a convenient method for managing a few domains that don’t require dynamic configuration. Maps, on the other hand, will let you handle many domains efficiently without your configuration file becoming a nightmare, and they allow you to update your domain mappings dynamically.

Want to stay up to date on similar topics? Subscribe to this blog! You can also follow us on Twitter and join the conversation on Slack.

Interested in advanced security and administrative features? HAProxy Enterprise is the world’s fastest and most widely used software load balancer. It powers modern application delivery at any scale and in any environment, providing the utmost performance, observability, and security. Organizations harness its cutting-edge features and enterprise suite of add-ons, backed by authoritative expert support and professional services. Ready to learn more? Sign up for a free trial.

Subscribe to our blog. Get the latest release updates, tutorials, and deep-dives from HAProxy experts.