Wazuh with Ansible - Automated Deployment Guide

Ansible enables automated installation and configuration of all Wazuh 4.14 components across any number of hosts. The official wazuh-ansible repository provides roles for the indexer, server, dashboard, and agents, supporting both single-node and multi-node architectures.

Prerequisites

Control node (Ansible controller)

  • Ansible 2.12 or later
  • Python 3.9+
  • SSH access to target hosts
  • sudo privileges on target hosts

Verify the Ansible version:

ansible --version

Target hosts

Installing the collection

Install the official Wazuh roles via Ansible Galaxy:

ansible-galaxy collection install wazuh.wazuh

Or clone the repository directly:

git clone https://github.com/wazuh/wazuh-ansible.git -b v4.14.3
cd wazuh-ansible

Role structure

The wazuh.wazuh collection includes the following roles:

RolePurpose
wazuh.wazuh.wazuh_indexerInstall and configure Wazuh Indexer (OpenSearch)
wazuh.wazuh.wazuh_managerInstall and configure Wazuh Manager
wazuh.wazuh.wazuh_dashboardInstall and configure Wazuh Dashboard
wazuh.wazuh.wazuh_agentInstall and configure Wazuh Agent

Each role ships with configuration templates, service restart handlers, and parameter validation.

Inventory

Single-node configuration

File inventory/single-node.yml:

all:
  children:
    wazuh_indexer:
      hosts:
        wazuh-node1:
          ansible_host: 192.168.1.10
          ansible_user: deploy
          ansible_become: true
          indexer_node_name: wazuh-indexer-1

    wazuh_manager:
      hosts:
        wazuh-node1:
          manager_type: master

    wazuh_dashboard:
      hosts:
        wazuh-node1:

    wazuh_agent:
      hosts:
        web-server-1:
          ansible_host: 192.168.1.20
          ansible_user: deploy
          ansible_become: true
        db-server-1:
          ansible_host: 192.168.1.21
          ansible_user: deploy
          ansible_become: true

Multi-node configuration

File inventory/multi-node.yml:

all:
  children:
    wazuh_indexer:
      hosts:
        indexer-1:
          ansible_host: 192.168.1.10
          indexer_node_name: wazuh-indexer-1
        indexer-2:
          ansible_host: 192.168.1.11
          indexer_node_name: wazuh-indexer-2
        indexer-3:
          ansible_host: 192.168.1.12
          indexer_node_name: wazuh-indexer-3
      vars:
        ansible_user: deploy
        ansible_become: true

    wazuh_manager:
      hosts:
        manager-master:
          ansible_host: 192.168.1.20
          manager_type: master
        manager-worker:
          ansible_host: 192.168.1.21
          manager_type: worker
      vars:
        ansible_user: deploy
        ansible_become: true

    wazuh_dashboard:
      hosts:
        dashboard-1:
          ansible_host: 192.168.1.30
      vars:
        ansible_user: deploy
        ansible_become: true

    wazuh_agent:
      hosts:
        app-server-[1:10]:
          ansible_user: deploy
          ansible_become: true

Single-node playbook

File playbooks/wazuh-single-node.yml:

- name: Install Wazuh Indexer
  hosts: wazuh_indexer
  roles:
    - role: wazuh.wazuh.wazuh_indexer
      vars:
        indexer_cluster_name: wazuh-cluster
        indexer_node_master: true
        indexer_node_data: true
        indexer_network_host: "0.0.0.0"
        indexer_admin_password: "ChangeMe!SecureP@ss1"

- name: Install Wazuh Manager
  hosts: wazuh_manager
  roles:
    - role: wazuh.wazuh.wazuh_manager
      vars:
        wazuh_manager_config:
          cluster:
            disabled: true
          api:
            bind_addr: "0.0.0.0"

- name: Install Wazuh Dashboard
  hosts: wazuh_dashboard
  roles:
    - role: wazuh.wazuh.wazuh_dashboard
      vars:
        dashboard_server_host: "0.0.0.0"
        dashboard_server_port: 443
        indexer_url: "https://{{ hostvars[groups['wazuh_indexer'][0]]['ansible_host'] }}:9200"

Execute the playbook:

ansible-playbook -i inventory/single-node.yml playbooks/wazuh-single-node.yml

Multi-node playbook

File playbooks/wazuh-multi-node.yml:

- name: Install Wazuh Indexer cluster
  hosts: wazuh_indexer
  roles:
    - role: wazuh.wazuh.wazuh_indexer
      vars:
        indexer_cluster_name: wazuh-cluster
        indexer_cluster_initial_master_nodes:
          - wazuh-indexer-1
          - wazuh-indexer-2
          - wazuh-indexer-3
        indexer_discovery_seed_hosts:
          - "{{ hostvars['indexer-1']['ansible_host'] }}"
          - "{{ hostvars['indexer-2']['ansible_host'] }}"
          - "{{ hostvars['indexer-3']['ansible_host'] }}"
        indexer_admin_password: "ChangeMe!SecureP@ss1"

- name: Install Wazuh Manager cluster
  hosts: wazuh_manager
  roles:
    - role: wazuh.wazuh.wazuh_manager
      vars:
        wazuh_manager_config:
          cluster:
            disabled: false
            name: wazuh-manager-cluster
            node_name: "{{ inventory_hostname }}"
            node_type: "{{ manager_type }}"
            key: "ChangeThisClusterKey123"
            nodes:
              - "{{ hostvars['manager-master']['ansible_host'] }}"
            port: 1516
            bind_addr: "0.0.0.0"
            hidden: false

- name: Install Wazuh Dashboard
  hosts: wazuh_dashboard
  roles:
    - role: wazuh.wazuh.wazuh_dashboard
      vars:
        dashboard_server_host: "0.0.0.0"
        indexer_url: "https://{{ hostvars[groups['wazuh_indexer'][0]]['ansible_host'] }}:9200"

- name: Install Wazuh Agents
  hosts: wazuh_agent
  roles:
    - role: wazuh.wazuh.wazuh_agent
      vars:
        wazuh_manager_address: "{{ hostvars['manager-master']['ansible_host'] }}"
        wazuh_agent_group: "default"

Execute the playbook:

ansible-playbook -i inventory/multi-node.yml playbooks/wazuh-multi-node.yml

Variables reference

Indexer variables

VariableDescriptionDefault
indexer_cluster_nameCluster namewazuh-cluster
indexer_node_nameNode namewazuh-indexer-1
indexer_node_masterMaster-eligible roletrue
indexer_node_dataData roletrue
indexer_network_hostBind address0.0.0.0
indexer_http_portHTTP port9200
indexer_transport_portTransport port9300
indexer_admin_passwordAdmin passwordSecretPassword
indexer_jvm_xmsJVM Heap min1g
indexer_jvm_xmxJVM Heap max1g

Manager variables

VariableDescriptionDefault
wazuh_manager_config.cluster.disabledDisable clusteringtrue
wazuh_manager_config.cluster.nameCluster namewazuh
wazuh_manager_config.cluster.node_typeNode type (master/worker)master
wazuh_manager_config.cluster.keyCluster key-
wazuh_manager_config.api.bind_addrAPI bind address0.0.0.0
wazuh_manager_config.api.portAPI port55000
wazuh_manager_authd.enabledEnable authdtrue
wazuh_manager_authd.use_passwordRequire enrollment passwordfalse

Agent variables

VariableDescriptionDefault
wazuh_manager_addressManager IP or DNS-
wazuh_agent_groupAgent groupdefault
wazuh_agent_nameAgent name{{ inventory_hostname }}
wazuh_agent_enrollment.enabledAuto-enrollmenttrue
wazuh_agent_enrollment.auth_passEnrollment password-

Agent enrollment via Ansible

Bulk agent deployment

To deploy agents across a host group, create a dedicated playbook:

- name: Deploy Wazuh agents
  hosts: wazuh_agent
  roles:
    - role: wazuh.wazuh.wazuh_agent
      vars:
        wazuh_manager_address: 192.168.1.20
        wazuh_agent_group: "{{ group_names | join(',') }}"
        wazuh_agent_enrollment:
          enabled: true
          auth_pass: "AgentEnrollmentPassword"

Password-based enrollment

Enable password-based agent authorization on the manager:

- name: Configure manager for password-based enrollment
  hosts: wazuh_manager
  roles:
    - role: wazuh.wazuh.wazuh_manager
      vars:
        wazuh_manager_authd:
          enabled: true
          use_password: true
          password: "AgentEnrollmentPassword"

Verify enrollment

ansible wazuh_manager -i inventory/multi-node.yml -m shell \
  -a "/var/ossec/bin/agent_control -l"

Custom configuration

Deploying custom rules

- name: Deploy custom rules
  hosts: wazuh_manager
  tasks:
    - name: Copy custom rules
      copy:
        src: files/local_rules.xml
        dest: /var/ossec/etc/rules/local_rules.xml
        owner: wazuh
        group: wazuh
        mode: "0640"
      notify: restart wazuh-manager

  handlers:
    - name: restart wazuh-manager
      service:
        name: wazuh-manager
        state: restarted

Configuring integrations

- name: Configure Slack integration
  hosts: wazuh_manager
  roles:
    - role: wazuh.wazuh.wazuh_manager
      vars:
        wazuh_manager_config:
          integration:
            - name: slack
              hook_url: "https://hooks.slack.com/services/xxx/yyy/zzz"
              level: 10
              alert_format: json

Troubleshooting

SSH connection failures

Symptoms: Ansible cannot connect to the target host.

Solution:

  1. Test SSH access manually:
ssh deploy@192.168.1.10
  1. Verify that the SSH key is present in authorized_keys on the target host

  2. Check the ansible.cfg configuration:

[defaults]
host_key_checking = False
timeout = 30

Package installation failures

Symptoms: the role fails during Wazuh package installation.

Solution:

  1. Verify Wazuh repository availability from the target host:
curl -s https://packages.wazuh.com/4.x/apt/ | head -5
  1. Confirm that the repository GPG key is installed

  2. For air-gapped networks, configure an offline repository

Indexer cluster fails to form

Symptoms: indexer nodes start but do not join the cluster.

Solution:

  1. Verify that indexer_discovery_seed_hosts contains all node IP addresses

  2. Confirm that port 9300 is open between nodes

  3. Ensure indexer_cluster_initial_master_nodes lists all node names

Agent fails to register

Symptoms: the agent is installed but does not appear in the agent list.

Solution:

  1. Test manager reachability from the agent host:
telnet 192.168.1.20 1515
  1. If password-based enrollment is enabled, verify that passwords match on the manager and agent

  2. Check agent logs:

tail -50 /var/ossec/logs/ossec.log

Additional resources

Last updated on