Automating Zabbix Deployments with Ansible
Manual Zabbix deployments accumulate drift: package versions diverge across distros, hardening steps disappear, agent configs fall out of sync. Two Ansible roles cover the whole stack: database, server, proxies, and agents, all idempotent.
The Problem
Manual Zabbix installs across a mixed RHEL and Ubuntu fleet accumulate drift fast. Package versions diverge between environments. Some hosts skip the MySQL anonymous user removal step. Agent configs end up with the wrong Server= IP or a missing ServerActive= line. Every new host is a one-off.
Three specific problems drove this:
- No repeatable way to add a new agent to an existing deployment without SSH one-liners
- Database hardening varies across hosts: root passwords stay at defaults on some, the test database stays on others
community.zabbix4.x changed how Ansible API modules work in a non-obvious way that breaks the first deployment attempt
The goal: one playbook, idempotent, handles the full stack from bare host to registered agent.
The Architecture
Two roles, one dependency chain. ansible-zabbix delegates database setup to ansible-mysql when deploying a server with a MySQL backend. The roles stay separate because ansible-mysql handles any MySQL deployment, not only Zabbix, and switching to PostgreSQL means changing db_backend in one place.
graph LR
subgraph "ansible-zabbix"
S["zabbix_function: server"]
P["zabbix_function: proxy"]
A["zabbix_function: agent"]
end
AM["ansible-mysql"]
S -->|"db_backend: mysql"| AM
subgraph "Tag execution order"
T1["server.install"] --> T2["proxy.install"] --> T3["agent.install"] --> T4["add.proxy"] --> T5["add.host"]
end
A single site.yml with four inventory groups drives the whole deployment. Tags control which tasks run, so you can re-run individual stages without touching the rest of the stack.
The four inventory groups:
ZABBIX_SERVER— the Zabbix server hostZABBIX_PROXIES— any proxy hosts (optional)ZABBIX_AGENTS— monitored hostsZABBIX_API— a dedicated entry for API operations (more on this below)
The tag execution order matters: the server schema must exist before proxies can register, and proxies must register before agents that route through them.
Three Things Worth Knowing
The ZABBIX_API Inventory Group
community.zabbix 4.x changed how the zabbix_host and zabbix_proxy modules work. They now use Ansible’s httpapi persistent connection plugin instead of making direct HTTP calls. This requires a dedicated inventory host configured with ansible_connection=httpapi.
It is not a real server. It is an inventory entry that points at the Zabbix web interface IP:
[ZABBIX_API]
zabbix-api ansible_host=192.168.1.10
[ZABBIX_API:vars]
ansible_connection=httpapi
ansible_network_os=community.zabbix.zabbix
ansible_user=Admin
ansible_httpapi_port=80
ansible_httpapi_use_ssl=false
ansible_httpapi_validate_certs=false
Without this group, add.host and add.proxy fail with connection errors that mention network connectivity. The real cause is the missing httpapi host.
The ansible_password for this host must go in its own group_vars file. Putting it in hosts.ini breaks Jinja2 templating:
# inventory/group_vars/ZABBIX_API.yml
ansible_password: "{{ zabbix_admin_password | default('zabbix') }}"
MySQL 8.0 Compatibility
Two breaking changes surface when targeting MySQL 8.0.
Auth plugin. MySQL 8.0 defaults to caching_sha2_password. Some Zabbix versions and older clients require mysql_native_password. The role exposes mysql_auth_plugin to switch:
# group_vars/ZABBIX_SERVER.yml
mysql_auth_plugin: mysql_native_password
Binary log expiry. MySQL 8.0 deprecated expire_logs_days, replacing it with binlog_expire_logs_seconds. The role detects the MySQL version and writes the correct directive to my.cnf. No manual intervention needed.
If MySQL is on the target host, run the role with --tags mysql to re-apply the configuration. The role is idempotent and leaves existing databases and users in place.
Decouple API Registration from Installation
add.host and add.proxy are separate tags from agent.install and proxy.install.
The Zabbix server must complete its database schema import and start its web interface before any API call succeeds. On a fresh install that takes 30 to 60 seconds after the package starts running. Running registration in the same play as installation causes the API to return 500 errors while the schema import is still in progress.
The workflow: run the installs, confirm the Zabbix web UI loads, then run registration as a second step:
# Install agents
ansible-playbook -i inventory/hosts.ini site.yml --tags agent.install -l ZABBIX_AGENTS
# After the web UI responds — register hosts
ansible-playbook -i inventory/hosts.ini site.yml --tags add.host -l ZABBIX_AGENTS
This lets you add new hosts to an existing deployment without touching the server or any existing agents.
Tutorial: Deploy Zabbix Server and Agent from Scratch
Two hosts: one for the Zabbix server, one as a monitored agent. Both running RHEL 8/9, Ubuntu 24.04, or Debian 12. The server needs at least 2 GB RAM for MySQL and the Zabbix server process to run alongside each other.
Prerequisites
- Ansible 2.14+ on the control node
- Both hosts reachable via SSH with
becomeaccess - Python 3 on both managed hosts
Step 1: Install the Roles and Collections
ansible-galaxy install git+https://github.com/Sifungurux/ansible-zabbix
ansible-galaxy install git+https://github.com/Sifungurux/ansible-mysql
ansible-galaxy collection install community.zabbix community.mysql
Step 2: Create the Inventory
# inventory/hosts.ini
[ZABBIX_SERVER]
zabbix01.example.com
[ZABBIX_PROXIES]
# proxy01.example.com
[ZABBIX_AGENTS]
web01.example.com
[ZABBIX_API]
zabbix-api ansible_host=zabbix01.example.com
[ZABBIX_API:vars]
ansible_connection=httpapi
ansible_network_os=community.zabbix.zabbix
ansible_user=Admin
ansible_httpapi_port=80
ansible_httpapi_use_ssl=false
ansible_httpapi_validate_certs=false
Step 3: Configure group_vars
# inventory/group_vars/all.yml
zabbix_version: "7.4"
zabbix_server: 192.168.1.10
zabbix_proxies: []
# inventory/group_vars/ZABBIX_SERVER.yml
zabbix_function: server
db_backend: mysql
db_pass: "CHANGE_ME_strong_root_password"
zabbix_db: zabbix
zabbix_user: zabbix
zabbix_pass: "CHANGE_ME_strong_db_password"
zabbix_admin_password: "CHANGE_ME_strong_admin_password"
# inventory/group_vars/ZABBIX_AGENTS.yml
zabbix_function: agent
# inventory/group_vars/ZABBIX_API.yml
ansible_password: "{{ zabbix_admin_password | default('zabbix') }}"
Replace every CHANGE_ME value before running. Use ansible-vault encrypt inventory/group_vars/ZABBIX_SERVER.yml to keep credentials out of version control.
Step 4: Write site.yml
---
- name: Deploy Zabbix server
hosts: ZABBIX_SERVER
become: true
roles:
- role: ansible-zabbix
- name: Deploy Zabbix agents
hosts: ZABBIX_AGENTS
become: true
roles:
- role: ansible-zabbix
Step 5: Deploy
Run in order. The server must respond on its web interface before the add.host step.
# 1. Install and configure the Zabbix server and its MySQL database
ansible-playbook -i inventory/hosts.ini site.yml --tags server.install -l ZABBIX_SERVER
# 2. Install a local agent on the server
# The built-in Zabbix server self-monitoring host checks 127.0.0.1:10050
ansible-playbook -i inventory/hosts.ini site.yml --tags agent.install -l ZABBIX_SERVER
# 3. Install agents on monitored hosts
ansible-playbook -i inventory/hosts.ini site.yml --tags agent.install -l ZABBIX_AGENTS
# 4. Open the web UI at http://<server-ip>/zabbix and confirm it loads, then register hosts
ansible-playbook -i inventory/hosts.ini site.yml --tags add.host -l ZABBIX_AGENTS
Result
The Zabbix web UI shows web01.example.com in the Linux servers host group, linked to Template OS Linux, with the agent availability indicator green. The built-in Zabbix server host at 127.0.0.1 also shows green.
Adding another host later takes two commands: agent.install on the new host, then add.host once the agent is running.
What’s Next
Three items remain on the list:
- PostgreSQL backend — the role has
db_backend: pgsqlbut the PostgreSQL path needs end-to-end testing. The MySQL path works; the role wires up pgsql but no one has tested past a fresh install. - TLS between agents and server —
tls: trueenables the config, but a full walkthrough with certificate generation is missing. - Molecule tests — the role runs against real VMs in CI but has no Molecule scenario. A converge and verify cycle across RHEL 9 and Ubuntu 24.04 would catch regressions before they reach main.
Need this set up for your team?
Automation Sprint →