Search code examples
linuxloadcontainershaproxylxd

HAProxy and LXD Containers - Route based on naked domain, specific sub-domain and all other sub-domains


I have tried searching for a similar solution to the problem I am having, but could not find anything.

I have setup some LXD containers, one is HAProxy which is recieving port 80 traffic from the host's public IP address. HAProxy thens sends the traffic to the correct container depending on the domain.

The issue I am having is, even though I have set The domain_specific_subdomain ACL rule for subdomain.example.com, the other domain_root ACL rule takes precedant.

Containers:

  • lxd-container-web1: Use if root domain, or any other subdomain, e.g. (www., dev.) 'example.com'
  • lxd-container-web2: Use only if this specific subdomain 'subdomain.example.com'

Question: How can I have this so the naked domain example.com and all other subdomains (e.g. www., dev., etc) go to this backend backend_web1 except for subdomain.example.com which should go to the: backend_web2 backend.

This is my config file:

global
        log /dev/log    local0
        log /dev/log    local1 notice
        chroot /var/lib/haproxy
        stats socket /run/haproxy/admin.sock mode 660 level admin
        stats timeout 30s
        user haproxy
        group haproxy
        daemon

        # Default SSL material locations
        ca-base /etc/ssl/certs
        crt-base /etc/ssl/private

        # Default ciphers to use on SSL-enabled listening sockets.
        # For more information, see ciphers(1SSL). This list is from:
        #  https://hynek.me/articles/hardening-your-web-servers-ssl-ciphers/
        ssl-default-bind-ciphers ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+3DES:!aNULL:!MD5:!DSS
        ssl-default-bind-options no-sslv3

defaults
        log     global
        mode    http
        option  httplog
        option  dontlognull
        option   forwardfor
        option   http-server-close
        timeout connect 5000
        timeout client  50000
        timeout server  50000
        errorfile 400 /etc/haproxy/errors/400.http
        errorfile 403 /etc/haproxy/errors/403.http
        errorfile 408 /etc/haproxy/errors/408.http
        errorfile 500 /etc/haproxy/errors/500.http
        errorfile 502 /etc/haproxy/errors/502.http
        errorfile 503 /etc/haproxy/errors/503.http
        errorfile 504 /etc/haproxy/errors/504.http


# Frontend
frontend www_frontend
        bind *:80     # Bind to port 80 (www) on the container


        # Covers naked domain and all subdomains
        acl domain_root hdr(host) -i example.com

        # Specific sub-domain
        acl domain_specific_subdomain hdr(host) -i subdomain.example.com


        # Redirect the connection to the proper server cluster, depending on the match.
        use_backend backend_web1 if domain_root
        use_backend backend_web2 if domain_specific_subdomain


# Domain dependent containers
backend backend_web1
        balance leastconn

        # We set the X-Client-IP HTTP header. This is useful if we want the web server to know the real client IP.
        http-request set-header X-Client-IP %[src]

        # This backend, named here "backend_web1", directs to container "lxd-container-web1.lxd" (hostname).
        server web1 lxd-container-web1:80 check

backend backend_web2
        balance leastconn
        http-request set-header X-Client-IP %[src]
        server web2 lxd-container-web2.lxd:80 check

Solution

  • From the haproxy docs:

    There may be as many "use_backend" rules as desired. All of these rules are evaluated in their declaration order, and the first one which matches will assign the backend.

    So if you switch the use_backend line ordering, it should work.

        # Redirect the connection to the proper server cluster, depending on the match.
        use_backend backend_web2 if domain_specific_subdomain
        use_backend backend_web1 if domain_root
    

    Or better yet, maybe default_backend would be better here:

        # Use backend_web2 if ACL matches
        use_backend backend_web2 if domain_specific_subdomain
    
        # Otherwise, default to backend_web1
        default_backend backend_web1