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 definition begins with acl. |
| The name of the ACL. The name is arbitrary, but it’s good form to make them understandable. |
| 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 |
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.