Ansible role for installing and configuring a HAProxy loadbalancer
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Simon Cornet e05e2242de
All checks were successful
ci/woodpecker/push/linting Pipeline was successful
docs: improve readme
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-05 17:29:47 +02:00
.woodpecker chore(package): update cr.simoncor.net/dockerhub/davidanson/markdownlint-cli2 docker tag to v0.23.3 2026-09-20 05:47:58 +00:00
defaults feat: add k3s like configs 2026-04-17 14:04:23 +02:00
handlers feat: add alpine support 2026-04-17 14:13:25 +02:00
meta feat: add alpine support 2026-04-17 14:13:25 +02:00
tasks chore: migrate bare ansible_* facts to ansible_facts[] syntax 2026-05-19 09:19:04 +02:00
templates feat: add k3s like configs 2026-04-17 14:04:23 +02:00
.ansible-lint fix: exclude only sops yml files from lint 2026-07-14 16:23:41 +02:00
.editorconfig chore: add .editorconfig and .gitattributes 2026-05-27 14:23:45 +02:00
.gitattributes chore: add .editorconfig and .gitattributes 2026-05-27 14:23:45 +02:00
.gitignore feat: initial commit 2026-04-17 13:34:09 +02:00
.markdownlint-cli2.jsonc style: ignore line length for markdown tables 2026-05-15 13:44:01 +02:00
.sops.yaml fix: correct sops regex to ya?ml 2026-07-14 16:01:48 +02:00
.yamllint chore: exclude .yml files from lint configs 2026-07-14 16:17:24 +02:00
ansible.cfg chore: sync linting and config files from common 2026-07-14 11:33:15 +02:00
readme.md docs: improve readme 2026-10-05 17:29:47 +02:00
renovate.json feat: initial commit 2026-04-17 13:34:09 +02:00

Ansible Role: HAProxy

Install and configure the HAProxy load balancer with flexible frontends, backends, and an optional stats listener.

Requirements

Operating System Notes
Debian / Ubuntu Installed with apt (state: latest), service managed by systemd
Alpine Installed with apk (state: latest), service managed by service

The role has no role dependencies (meta/main.yaml).

Variables

The role is driven by five variables. haproxy_global, haproxy_defaults and haproxy_stats are dictionaries, haproxy_frontends and haproxy_backends are lists.

Variable Required Default Description
haproxy_global No See Global Settings Dictionary rendered into the global section
haproxy_defaults No See Default Settings Dictionary rendered into the defaults section
haproxy_stats No See Stats Listener Dictionary rendered into an optional listen stats
haproxy_frontends No [] List of frontends, see Frontends
haproxy_backends No [] List of backends, see Backends

Overriding one of the dictionaries replaces the whole default dictionary (Ansible does not merge variables by default), so repeat every key the template needs. The template requires log, mode, timeout_connect, timeout_client and timeout_server in haproxy_defaults, and log and daemon in haproxy_global. When haproxy_stats.enabled is true, all haproxy_stats keys are required.

The generated /etc/haproxy/haproxy.cfg is validated with haproxy -c -f before it is installed, and HAProxy is restarted when it changes.

Global Settings

Variable Required Default Description
haproxy_global.log No List with /dev/log local2 Log target (string or list)
haproxy_global.stats_socket No /var/lib/haproxy/stats user haproxy group haproxy mode 660 level admin Stats socket configuration
haproxy_global.stats_timeout No "" (not rendered) Stats timeout (optional, e.g., "30s")
haproxy_global.chroot No /var/lib/haproxy Chroot directory
haproxy_global.user No haproxy User to run as
haproxy_global.group No haproxy Group to run as
haproxy_global.daemon No true Run as daemon
haproxy_global.pidfile No /var/run/haproxy.pid PID file location (optional)
haproxy_global.maxconn No 35000 Maximum connections (optional)

Default Settings

Variable Required Default Description
haproxy_defaults.log No global Log setting
haproxy_defaults.mode No tcp Default mode (tcp/http)
haproxy_defaults.options No List with tcplog Options list, rendered as option <item> (a single option key is a fallback)
haproxy_defaults.timeout_check No 5s Health check timeout (optional)
haproxy_defaults.timeout_connect No 10s Connection timeout
haproxy_defaults.timeout_client No 600s Client timeout
haproxy_defaults.timeout_server No 600s Server timeout
haproxy_defaults.timeout_tunnel No "" (not rendered) Tunnel timeout (optional)

Stats Listener

Variable Required Default Description
haproxy_stats.enabled No false Enable stats interface
haproxy_stats.bind No 0.0.0.0:9000 Bind address for stats
haproxy_stats.mode No http Stats mode
haproxy_stats.uri No / Stats URI path
haproxy_stats.show_node No true Show node name
haproxy_stats.refresh No 10s Refresh interval
haproxy_stats.auth No admin:password Basic auth credentials

Frontends

Variable Required Default Description
haproxy_frontends No [] List of frontends
haproxy_frontends[].name Yes - Frontend name
haproxy_frontends[].bind Yes - Bind address and port
haproxy_frontends[].default_backend Yes - Default backend name
haproxy_frontends[].mode No - Override default mode
haproxy_frontends[].options No [] Extra lines, rendered verbatim

Backends

Variable Required Default Description
haproxy_backends No [] List of backends
haproxy_backends[].name Yes - Backend name
haproxy_backends[].balance No - Load balancing algorithm
haproxy_backends[].hash_type No - Hash type for balancing
haproxy_backends[].mode No - Override default mode
haproxy_backends[].stick_table No - Stick table configuration
haproxy_backends[].acls No [] List of ACL rules
haproxy_backends[].tcp_request No [] TCP request rules
haproxy_backends[].tcp_response No [] TCP response rules
haproxy_backends[].stick No [] Stick rules
haproxy_backends[].options No [] Extra lines, rendered verbatim (e.g. option tcp-check)
haproxy_backends[].http_check No [] HTTP health check directives
haproxy_backends[].tcp_check No [] TCP health check directives
haproxy_backends[].servers Yes - List of backend servers
haproxy_backends[].servers[].name Yes - Server name
haproxy_backends[].servers[].address Yes - Server address:port
haproxy_backends[].servers[].check No false Enable health checks
haproxy_backends[].servers[].check_port No - Health check port (if different)
haproxy_backends[].servers[].send_proxy No false Enable send-proxy
haproxy_backends[].servers[].extra_params No - Additional server parameters

Usage

This repository has no playbook.yaml. The role is consumed by other playbooks, which install it with ansible-galaxy and set the variables in the inventory. Keep the stats auth credentials in sops-encrypted inventory variables, the admin:password values below are placeholders.

Examples

Teleport Example

Configuration for Teleport with SSL session persistence and advanced health checks:

---
- name: "Deploy HAProxy for Teleport"
  hosts: "haproxy_servers"
  become: true

  vars:

    # global settings with multiple log targets
    haproxy_global:
      log:
        - "/dev/log local0"
        - "/dev/log local1 notice"
      stats_socket: "/var/lib/haproxy/stats level admin"
      chroot: "/var/lib/haproxy"
      user: "haproxy"
      group: "haproxy"
      daemon: true

    # defaults with multiple options
    haproxy_defaults:
      log: "global"
      mode: "tcp"
      options:
        - "httplog"
        - "dontlognull"
      timeout_connect: "5000"
      timeout_client: "10m"
      timeout_server: "10m"
      timeout_tunnel: "10m"

    # frontends
    haproxy_frontends:
      - name: "hafrontend"
        bind: "*:443"
        mode: "tcp"
        default_backend: "teleport"

    # backends with http-check and tcp-check
    haproxy_backends:
      - name: "teleport"
        mode: "tcp"
        balance: "roundrobin"
        stick_table: "type binary len 2048 size 300k expire 30m"
        acls:
          - "clienthello req.ssl_hello_type 1"
          - "serverhello res.ssl_hello_type 2"
        tcp_request:
          - "inspect-delay 10s"
          - "content accept if clienthello"
        tcp_response:
          - "content accept if serverhello"
        stick:
          - "on req.payload_lv(43,1) if clienthello"
          - "store-response res.payload_lv(43,1) if serverhello"
        options:
          - "option httpchk GET /healthz"
          - "option tcp-check"
        http_check:
          - "expect status 200"
        tcp_check:
          - "connect"
        servers:

          # teleport node 1
          - name: "teleport-prod-01"
            address: "192.168.10.120:443"
            check: true
            check_port: "3000"
            send_proxy: true
            extra_params: "check-ssl verify none"

          # teleport node 2
          - name: "teleport-prod-02"
            address: "192.168.10.121:443"
            check: true
            check_port: "3000"
            send_proxy: true
            extra_params: "check-ssl verify none"

  roles:
    - "haproxy"

K3s Cluster Example

Configuration for K3s cluster with IP-based session persistence:

---
- name: "Deploy HAProxy for K3s Cluster"
  hosts: "haproxy_servers"
  become: true

  vars:

    # global settings
    haproxy_global:
      log:
        - "/dev/log local0"
        - "/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: true

    # defaults settings
    haproxy_defaults:
      log: "global"
      mode: "tcp"
      options:
        - "tcplog"
        - "dontlognull"
      timeout_connect: "5000"
      timeout_client: "50000"
      timeout_server: "50000"

    # frontends
    haproxy_frontends:
      - name: "fe-k3s-http"
        bind: "192.168.10.17:80"
        default_backend: "be-k3s-http"

      - name: "fe-k3s-https"
        bind: "192.168.10.17:443"
        default_backend: "be-k3s-https"

      - name: "fe-k3s-api"
        bind: "192.168.10.17:6443"
        default_backend: "be-k3s-nodes"

    # backends with IP-based sticky sessions
    haproxy_backends:
      - name: "be-k3s-http"
        balance: "roundrobin"
        stick_table: "type ip size 200k expire 30m"
        stick:
          - "on src"
        options:
          - "option tcp-check"
        servers:
          - name: "k3s-node01"
            address: "192.168.10.21:80"
            check: true
          - name: "k3s-node02"
            address: "192.168.10.22:80"
            check: true
          - name: "k3s-node03"
            address: "192.168.10.23:80"
            check: true

      - name: "be-k3s-https"
        balance: "roundrobin"
        stick_table: "type ip size 200k expire 30m"
        stick:
          - "on src"
        options:
          - "option tcp-check"
        servers:
          - name: "k3s-node01"
            address: "192.168.10.21:443"
            check: true
          - name: "k3s-node02"
            address: "192.168.10.22:443"
            check: true
          - name: "k3s-node03"
            address: "192.168.10.23:443"
            check: true

      - name: "be-k3s-nodes"
        balance: "roundrobin"
        options:
          - "option tcp-check"
        servers:
          - name: "k3s-node01"
            address: "192.168.10.21:6443"
            check: true
          - name: "k3s-node02"
            address: "192.168.10.22:6443"
            check: true
          - name: "k3s-node03"
            address: "192.168.10.23:6443"
            check: true

  roles:
    - "haproxy"

WAF Example with SSL Persistence

---
- name: "Deploy HAProxy Load Balancer"
  hosts: "haproxy_servers"
  become: true

  vars:

    # enable stats interface
    haproxy_stats:
      enabled: true
      bind: "192.168.32.10:9000"
      mode: "http"
      uri: "/"
      show_node: true
      refresh: "10s"
      auth: "admin:password"

    # frontends
    haproxy_frontends:

      # http frontend
      - name: "http-in"
        bind: "192.168.32.15:80"
        default_backend: "waf_siempie_tools_http"

      # https frontend
      - name: "https-in"
        bind: "192.168.32.15:443"
        default_backend: "waf_siempie_tools_https"

    # backends
    haproxy_backends:

      # http backend with simple load balancing
      - name: "waf_siempie_tools_http"
        balance: "source"
        hash_type: "consistent"
        options:
          - "option httpchk GET /health"
        servers:

          # waf0.siempie.tools
          - name: "waf0.siempie.tools"
            address: "192.168.32.20:80"
            check: true
            send_proxy: true

          # waf1.siempie.tools
          - name: "waf1.siempie.tools"
            address: "192.168.32.21:80"
            check: true
            send_proxy: true

      # https backend with SSL session persistence
      - name: "waf_siempie_tools_https"
        balance: "roundrobin"
        stick_table: "type binary len 2048 size 300k expire 30m"
        acls:
          - "clienthello req.ssl_hello_type 1"
          - "serverhello res.ssl_hello_type 2"
        tcp_request:
          - "inspect-delay 10s"
          - "content accept if clienthello"
        tcp_response:
          - "content accept if serverhello"
        stick:
          - "on req.payload_lv(43,1) if clienthello"
          - "store-response res.payload_lv(43,1) if serverhello"
        options:
          - "option httpchk GET /health"
        servers:

          # waf0.siempie.tools
          - name: "waf0.siempie.tools"
            address: "192.168.32.20:443"
            send_proxy: true
            check: true
            extra_params: "check-ssl verify none"

          # waf1.siempie.tools
          - name: "waf1.siempie.tools"
            address: "192.168.32.21:443"
            send_proxy: true
            check: true
            extra_params: "check-ssl verify none"

  roles:
    - "haproxy"