I tend to build a lot of web applications in NodeJS using the Express.js webserver. When you have a few of these apps running on one server, you generally want to run them on unique ports and put some kind of proxy in front of them. Nginx works great for this and Apache can be another decent, though more bloated, alternative. Recently I decided to branch out for the sake of variety and to learn something new. HAProxy filled that role.

For the uninformed, HAProxy is more than just a reverse proxy; it's a high performance load balancer. Sites with lots of traffic will use something like HAProxy to funnel traffic to a cluster of web servers or even balance taffic between database servers. But what happens when we want to route multiple domains (or subdomains) to different hosts or clusters?

Setting up a single proxy

First lets take a look at the basics of proxying to an app server.

# config for haproxy 1.5.x

global
        log 127.0.0.1   local0
        log 127.0.0.1   local1 notice
        maxconn 4096
        user haproxy
        group haproxy
        daemon

defaults
        log     global
        mode    http
        option  httplog
        option  dontlognull
        option forwardfor
        option http-server-close
        stats enable
        stats auth someuser:somepassword
        stats uri /haproxyStats

frontend http-in
        bind :80
        default_backend web-app-cluster

backend web-app-cluster
        balance leastconn
        option httpclose
        cookie JSESSIONID prefix
        server node1 10.0.0.1:8080 cookie A check
        server node2 10.0.0.2:8080 cookie A check
        server node3 10.0.0.3:8080 cookie A check

So this is a pretty basic config that will loadbalance across 3 application servers, each of which is on a unqiue IP and probably on its own dedicated machine. Generally you'll also want to run your load balancer(s) on a different server.

So what does this all mean? global and defaults should be pretty self-explanatory, then we have a frontend and abackend. The frontend, as you can see, tells HAProxy what to bind to and defines a default backend. There are a lot of things that can be specified in the front end and you can also have multiple frontend definitions (for example, if you wanted to provide an unsecure route running on port 80 and SSL on port 443 and have different, or the same, backends for each). We'll go over some other options in the multiple domain example.

Diving into multiple domains and ACLs

Now lets take a look at how to route to multiple domains based on matching specific domain names.

# config for haproxy 1.5.x

global
        log 127.0.0.1   local0
        log 127.0.0.1   local1 notice
        maxconn 4096
        user haproxy
        group haproxy
        daemon

defaults
        log     global
        mode    http
        option  httplog
        option  dontlognull
        option forwardfor
        option http-server-close
        stats enable
        stats auth someuser:somepassword
        stats uri /haproxyStats

frontend http-in
        bind *:80

        # Define hosts
        acl host_bacon hdr(host) -i ilovebacon.com
        acl host_milkshakes hdr(host) -i bobsmilkshakes.com

        ## figure out which one to use
        use_backend bacon_cluster if host_bacon
        use_backend milshake_cluster if host_milkshakes

backend baconcluster
        balance leastconn
        option httpclose
        option forwardfor
        cookie JSESSIONID prefix
        server node1 10.0.0.1:8080 cookie A check
        server node1 10.0.0.2:8080 cookie A check
        server node1 10.0.0.3:8080 cookie A check


backend milshake_cluster
        balance leastconn
        option httpclose
        option forwardfor
        cookie JSESSIONID prefix
        server node1 10.0.0.4:8080 cookie A check
        server node1 10.0.0.5:8080 cookie A check
        server node1 10.0.0.6:8080 cookie A check

So here we are routing between two applications; ilovebacon.com and bobsmilkshakes.com. Each one has its own cluster of app servers that we want to load balance between. Lets take a closer look at where all the magic happens in the frontend.

frontend http-in
        bind *:80

        # Define hosts
        acl host_bacon hdr(host) -i ilovebacon.com
        acl host_milkshakes hdr(host) -i bobsmilkshakes.com

        ## figure out which one to use
        use_backend bacon_cluster if host_bacon
        use_backend milshake_cluster if host_milkshakes

If you've ever used nginx or Apache as reverse proxies, youd generally set things up using virtual hosts. HAProxy uses the notion of access control lists (acl) which can be used to direct traffic.

After we bind to port 80, we set up two acls. The hdr (short for header) checks the hostname header. We also specify -i to make sure its case insensitive, then provide the domain name that we want to match. You could also setup acls to match routes, file types, file names, etc. If you want to know more, feel free to check the docs. So now we effectively have two variables; host_bacon and host_milkshakes. Then we tell HAProxy what backend to use by checking to see if the variable is true or not. One other thing that we could do after the use_backend checks is specify a default_backend <backend name> like we did in the first example to act as a catch-all to traffic hitting our load balancer that doesn't match either of the domain names.

Next time, I'll go over how to add HTTPS/SSL support to your websites.