How to use HTTP host header to choose HAProxy backend

Dynamically choose HAProxy backend depending on the HTTP host header, Lua programming language and environment variable.

HAProxy version.

$ haproxy -v
HA-Proxy version 1.7.5-2 2017/05/17
Copyright 2000-2017 Willy Tarreau <[email protected]>

Default HAProxy configuration.

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/
	# An alternative list with additional directives can be obtained from
	#  https://mozilla.github.io/server-side-tls/ssl-config-generator/?server=haproxy
	ssl-default-bind-ciphers ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:RSA+AESGCM:RSA+AES:!aNULL:!MD5:!DSS
	ssl-default-bind-options no-sslv3

defaults
	log	global
	mode	http
	option	httplog
	option	dontlognull
        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

Inspect systemd service file to learn that you can use /etc/default/haproxy file to define additional environment variables.

$ cat /lib/systemd/system/haproxy.service
[Unit]
Description=HAProxy Load Balancer
Documentation=man:haproxy(1)
Documentation=file:/usr/share/doc/haproxy/configuration.txt.gz
After=network.target syslog.service
Wants=syslog.service

[Service]
Environment="CONFIG=/etc/haproxy/haproxy.cfg" "PIDFILE=/run/haproxy.pid"
EnvironmentFile=-/etc/default/haproxy
ExecStartPre=/usr/sbin/haproxy -f $CONFIG -c -q $EXTRAOPTS
ExecStart=/usr/sbin/haproxy-systemd-wrapper -f $CONFIG -p $PIDFILE $EXTRAOPTS
ExecReload=/usr/sbin/haproxy -f $CONFIG -c -q $EXTRAOPTS
ExecReload=/bin/kill -USR2 $MAINPID
KillMode=mixed
Restart=always

[Install]
WantedBy=multi-user.target

Use HAPROXY_SERVER_NAME variable to define fully qualified domain name of the HAProxy server. Unfortunatelly, you cannot use $(hostname --fqdn) command to set it on service start.

$ cat << EOF | sudo tee -a /etc/default/haproxy

# pass server name to haproxy
HAPROXY_SERVER_NAME="example.org"
EOF
# pass server name to haproxy
HAPROXY_SERVER_NAME="example.org"
$ cat /etc/default/haproxy 
# Defaults file for HAProxy
#
# This is sourced by both, the initscript and the systemd unit file, so do not
# treat it as a shell script fragment.

# Change the config file location if needed
#CONFIG="/etc/haproxy/haproxy.cfg"

# Add extra flags here, see haproxy(1) for a few options
#EXTRAOPTS="-de -m 16"

# pass server name to haproxy
HAPROXY_SERVER_NAME="example.org"

Define Lua function to get hostname defined in HTTP host header.

$ cat << | sudo tee /etc/haproxy/destination_hostname.lua
core.register_fetches("destination_hostname", function(txn)
  local hostname = txn.sf:req_fhdr("host"):lower()
  return hostname
end)
EOF
core.register_fetches("destination_hostname", function(txn)
  local hostname = txn.sf:req_fhdr("host"):lower()
  return hostname
end)

Load Lua script inside global section.

global
  [...]
  lua-load /etc/haproxy/destination_hostname.lua

Define frontend and backends to use default backend for HAProxy address (this is why we need HAPROXY_SERVER_NAME environment variable), example.com and example.net backends for their respective addresses, nonexistent backend for every other ddress.

frontend development-frontend
  bind :80
  #bind :443 ssl crt /etc/ssl/cert/

  option httplog

  option forwardfor except 127.0.0.1
  option forwardfor header X-Real-IP

  #redirect scheme https code 301 if !{ ssl_fc }

  acl is-host-itself lua.destination_hostname() "${HAPROXY_SERVER_NAME}"
  http-request set-var(txn.destination_hostname) lua.destination_hostname()

  use_backend default if is-host-itself
  use_backend %[var(txn.destination_hostname)]
  default_backend nonexistent  


backend default
  server default 10.66.91.125:80

backend example.com
  server default 10.66.91.52:80

backend example.net
  server default 10.66.91.53:80

backend nonexistent
  server default 10.66.91.50:80

Sample log output for regular query to HAProxy hostname (default backend).

$ curl http://example.org
Jan 21 17:05:21 example haproxy[7858]: 10.66.91.165:32856 [21/Jan/2018:17:05:21.581] development-frontend default/default 0/0/0/4/5 200 9386 - - ---- 1/1/0/1/0 0/0 "GET / HTTP/1.1"

Sample log output for regular query to the hosted example.com domain (example.com backend).

$ curl http://example.com
Jan 21 17:06:09 example haproxy[7858]: 10.66.91.165:32868 [21/Jan/2018:17:06:09.845] development-frontend example.com/default 0/0/0/1/1 301 805 - - ---- 1/1/0/1/0 0/0 "GET / HTTP/1.1"

Sample log output for regular query to the hosted example.net domain (example.net backend).

$ curl http://example.net
Jan 21 17:06:12 example haproxy[7858]: 10.66.91.165:32874 [21/Jan/2018:17:06:12.170] development-frontend example.net/default 0/0/0/123/123 200 29158 - - ---- 1/1/0/1/0 0/0 "GET / HTTP/1.1"

Sample log output for regular query to the not hosted example.co.uk domain (nonexistent backend).

$ curl http://example.co.uk
Jan 21 17:06:46 example haproxy[7858]: 10.66.91.165:32880 [21/Jan/2018:17:06:46.662] development-frontend nonexistent/default 0/0/0/0/0 200 11174 - - ---- 1/1/0/1/0 0/0 "GET / HTTP/1.1"

About Milosz Galazka

Milosz is a Linux Foundation Certified Engineer working for a successful Polish company as a system administrator and a long time supporter of Free Software Foundation and Debian operating system. He is also open for new opportunities and challenges.