This guide provides Ansible playbooks to install and configure HAProxy with distro-aware package handling, security hardening, and production-ready configuration for Debian 12+, Ubuntu 24.04+, and RHEL 9+ compatible systems.
---
- name: Install HAProxy
hosts: haproxy
become: true
vars:
app_config_dir: /etc/haproxy
haproxy_user: haproxy
haproxy_group: haproxy
tasks:
- name: Install package on Debian/Ubuntu
apt:
name:
- haproxy
state: present
update_cache: true
when: ansible_os_family == "Debian"
notify: restart haproxy
- name: Install package on RHEL family
dnf:
name:
- haproxy
state: present
when: ansible_os_family == "RedHat"
notify: restart haproxy
- name: Create configuration directory
file:
path: "{{ app_config_dir }}"
state: directory
owner: "{{ haproxy_user }}"
group: "{{ haproxy_group }}"
mode: "0750"
- name: Enable and start service
systemd:
name: haproxy
state: started
enabled: true
daemon_reload: true
failed_when: false
- name: Verify binary is available
command: "haproxy --version"
register: app_version
changed_when: false
failed_when: false
- name: Show detected version output
debug:
var: app_version.stdout
handlers:
- name: restart haproxy
systemd:
name: haproxy
state: restarted
---
- name: Install and configure HAProxy for production
hosts: haproxy
become: true
vars:
app_config_dir: /etc/haproxy
haproxy_user: haproxy
haproxy_group: haproxy
backend_servers:
- name: web1
address: 10.0.1.10
port: 80
- name: web2
address: 10.0.1.11
port: 80
- name: web3
address: 10.0.1.12
port: 80
tasks:
- name: Install package on Debian/Ubuntu
apt:
name:
- haproxy
state: present
update_cache: true
when: ansible_os_family == "Debian"
notify: restart haproxy
- name: Install package on RHEL family
dnf:
name:
- haproxy
state: present
when: ansible_os_family == "RedHat"
notify: restart haproxy
- name: Create configuration directory
file:
path: "{{ app_config_dir }}"
state: directory
owner: "{{ haproxy_user }}"
group: "{{ haproxy_group }}"
mode: "0750"
- name: Generate HAProxy configuration
template:
src: haproxy.cfg.j2
dest: "{{ app_config_dir }}/haproxy.cfg"
owner: "{{ haproxy_user }}"
group: "{{ haproxy_group }}"
mode: "0640"
notify: validate and restart haproxy
- name: Enable and start service
systemd:
name: haproxy
state: started
enabled: true
daemon_reload: true
- name: Verify binary is available
command: "haproxy --version"
register: app_version
changed_when: false
failed_when: false
- name: Show detected version output
debug:
var: app_version.stdout
handlers:
- name: validate and restart haproxy
block:
- name: Validate HAProxy configuration
command: haproxy -c -f {{ app_config_dir }}/haproxy.cfg
register: config_validation
failed_when: config_validation.rc != 0
- name: Restart HAProxy
systemd:
name: haproxy
state: restarted
- name: restart haproxy
systemd:
name: haproxy
state: restarted
Create a template file templates/haproxy.cfg.j2:
global
log /dev/log local0
maxconn 4000
user {{ haproxy_user }}
group {{ haproxy_group }}
daemon
stats socket /var/run/haproxy/admin.sock mode 660 level admin
stats timeout 30s
# Modern SSL settings
ssl-default-bind-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384
ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
ssl-default-bind-options ssl-min-ver TLSv1.2 no-tls-tickets
# Performance tuning
tune.ssl.default-dh-param 2048
tune.bufsize 32768
tune.maxrewrite 1024
defaults
log global
mode http
option httplog
option dontlognull
option forwardfor
option http-server-close
timeout connect 5s
timeout client 60s
timeout server 60s
timeout tunnel 3600s
timeout http-keep-alive 10s
retries 3
option redispatch
# Stats page - RESTRICT ACCESS!
frontend stats
bind *:8404
mode http
stats enable
stats uri /stats
stats refresh 30s
{% if haproxy_stats_allowed_networks is defined %}
{% for network in haproxy_stats_allowed_networks %}
acl allowed_network_{{ loop.index }} src {{ network }}
{% endfor %}
http-request allow if {% for network in haproxy_stats_allowed_networks %}allowed_network_{{ loop.index }}{% if not loop.last %} or {% endif %}{% endfor %}
http-request deny
{% else %}
# WARNING: Stats page accessible to all IPs - restrict in production!
{% endif %}
stats admin if LOCALHOST
# Main HTTP frontend
frontend http_front
bind *:80
mode http
# Redirect to HTTPS
http-request redirect scheme https code 301 if !{ ssl_fc }
# HTTPS frontend with SSL termination
frontend https_front
bind *:443 ssl crt /etc/ssl/private/cert.pem alpn h2,http/1.1
mode http
option httpclose
# Security headers
rspadd Strict-Transport-Security: max-age=31536000;\ includeSubDomains;\ preload
rspadd X-Content-Type-Options: nosniff
rspadd X-Frame-Options: DENY
rspadd X-XSS-Protection: 1;\ mode=block
# ACLs for path-based routing
acl path_api path_beg /api
acl path_static path_beg /static /images /css /js
use_backend api_servers if path_api
use_backend static_servers if path_static
default_backend app_servers
# Application servers backend
backend app_servers
balance random(2)
option httpchk GET /health
{% for server in backend_servers %}
server {{ server.name }} {{ server.address }}:{{ server.port }} check
{% endfor %}
# API servers backend
backend api_servers
balance random(2)
option httpchk GET /api/health
{% for server in backend_servers %}
server {{ server.name }}_api {{ server.address }}:{{ server.port }} check
{% endfor %}
# Static content backend
backend static_servers
balance roundrobin
{% for server in backend_servers %}
server {{ server.name }}_static {{ server.address }}:{{ server.port }} check
{% endfor %}
For more complex deployments, create an Ansible role:
# roles/haproxy/tasks/main.yml
---
- name: Install HAProxy package
package:
name: haproxy
state: present
notify: restart haproxy
- name: Create HAProxy configuration directory
file:
path: /etc/haproxy
state: directory
owner: haproxy
group: haproxy
mode: '0750'
- name: Copy HAProxy configuration
template:
src: haproxy.cfg.j2
dest: /etc/haproxy/haproxy.cfg
owner: haproxy
group: haproxy
mode: '0640'
notify: validate and restart haproxy
- name: Enable and start HAProxy service
systemd:
name: haproxy
state: started
enabled: yes
daemon_reload: yes
- name: Configure firewall for HAProxy
include_tasks: firewall.yml
when: configure_firewall | default(true)
# roles/haproxy/handlers/main.yml
---
- name: validate and restart haproxy
block:
- name: Validate HAProxy configuration
command: haproxy -c -f /etc/haproxy/haproxy.cfg
register: config_validation
failed_when: config_validation.rc != 0
- name: Restart HAProxy
systemd:
name: haproxy
state: restarted
- name: restart haproxy
systemd:
name: haproxy
state: restarted
Example inventory file for HAProxy servers:
[haproxy]
haproxy1 ansible_host=10.0.0.10
haproxy2 ansible_host=10.0.0.11
[haproxy:vars]
haproxy_stats_allowed_networks=["10.0.0.0/8", "192.168.0.0/16"]
# Basic installation
ansible-playbook -i inventory haproxy-install.yml
# With extra variables
ansible-playbook -i inventory haproxy-install.yml --extra-vars "haproxy_version=3.3"
# Dry run to check changes
ansible-playbook -i inventory haproxy-install.yml --check
# Run only specific tags
ansible-playbook -i inventory haproxy-install.yml --tags "config,service"
We develop tailored automation solutions for:
Let’s discuss your requirements: office@linux-server-admin.com | Contact