Source: opensourceforu.com
What Is Cacti?
Cacti is an open-source network monitoring and graphing tool written in PHP, released under the GNU General Public License. It uses MySQL as its backend database and relies on RRDtool (Round-Robin Database tool) to store and render time-series performance data. Cacti has built-in SNMP support, making it well-suited for polling network devices such as routers, switches, and servers.
Deploying Cacti manually involves installing and configuring several interdependent components. This article walks through automating the entire process with Ansible, reducing a multi-step manual procedure to a single playbook invocation.
Environment Overview
The walkthrough uses the following environment:
- Target host: CentOS 6.8 virtual machine running on KVM
- Control node: Parabola GNU/Linux-libre x86_64 running Ansible 2.2.1.0
- Guest VM IP: 192.168.122.98
Security note: SELinux is disabled in this demonstration to simplify the walkthrough. Set SELINUX=disabled in /etc/selinux/config and reboot the VM only for testing purposes. Always enable and properly configure SELinux in production environments.
Inventory and Directory Structure
The Ansible project uses the following layout:
ansible/inventory/kvm/inventory
ansible/inventory/kvm/group_vars/all/all.yml
The inventory file defines the target CentOS VM:
centos ansible_host=192.168.122.98 ansible_connection=ssh ansible_user=root ansible_password=password
Add a corresponding entry to the control node’s /etc/hosts:
192.168.122.98 centosCode language: CSS (css)
The all.yml group variables file references sensitive values from an Ansible Vault file rather than storing them in plain text:
---
mysql_cacti_password_hash: "{{ vault_mysql_cacti_password_hash }}"
mysql_username: "{{ vault_mysql_user }}"
mysql_password: "{{ vault_mysql_password }}"Code language: JavaScript (javascript)
The main playbook file cacti.yml lives under ansible/playbooks/configuration/.
Managing Secrets with Ansible Vault
Ansible Vault encrypts sensitive files so that credentials are never stored in plain text. Set your preferred editor before creating a vault file:
$ export EDITOR=nanoCode language: JavaScript (javascript)
Create the vault file to hold MySQL credentials:
$ ansible-vault create inventory/kvm/group_vars/all/vault.yml
Ansible will prompt for an encryption password, then open your editor. Enter the credentials and save the file:
---
vault_mysql_cacti_password_hash: "*528573A4E6FE4F3E8B455F2F060EB6F63ECBECAA"
vault_mysql_user: "cacti"
vault_mysql_password: "cacti123"Code language: JavaScript (javascript)
To edit the vault file later, use:
$ ansible-vault edit inventory/kvm/group_vars/all/vault.yml
Ansible will prompt for the vault password before decrypting the file for editing.
Important: The password shown above (cacti123) is a placeholder for demonstration only. Use strong, unique credentials in any real deployment.
Play 1: Install Apache HTTP Server
The first play installs the Apache web server. It updates the package cache, installs httpd and supporting utilities, starts the service, and waits until port 80 is accepting connections before continuing.
---
- name: Install web server
hosts: centos
gather_facts: true
tags: [httpd]
tasks:
- name: Update the software package repository
yum:
name: '*'
update_cache: yes
- name: Install HTTP packages
package:
name: "{{ item }}"
state: latest
with_items:
- wget
- nano
- httpd
- httpd-devel
- name: Start the httpd server
service:
name: httpd
state: started
- wait_for:
port: 80Code language: JavaScript (javascript)
Play 2: Install MySQL, PHP, RRDtool, and SNMP
The second play installs Cacti’s core dependencies: MySQL, a full set of PHP extensions, RRDtool, and the SNMP utilities. After installation, both the SNMP daemon and the MySQL server are started, and the play waits for MySQL to become available on port 3306.
- name: Install MySQL, PHP packages
hosts: centos
become: yes
become_method: sudo
gather_facts: true
tags: [database-web]
tasks:
- name: Install database/web packages
package:
name: "{{ item }}"
state: latest
with_items:
- mysql
- mysql-server
- MySQL-python
- php-mysql
- php-pear
- php-common
- php-gd
- php-devel
- php
- php-mbstring
- php-cli
- php-process
- php-snmp
- net-snmp-utils
- net-snmp-libs
- rrdtool
- name: Start snmpd server
service:
name: snmpd
state: started
- name: Start mysqld server
service:
name: mysqld
state: started
- wait_for:
port: 3306Code language: JavaScript (javascript)

Play 3: Install Cacti from EPEL
Cacti is available in the EPEL (Extra Packages for Enterprise Linux) repository, which is not enabled on CentOS by default. This play imports the EPEL GPG key, adds the EPEL repository, updates the package cache, installs Cacti, and creates a dedicated MySQL database user for the application.
- name: Install Cacti
hosts: centos
become: yes
become_method: sudo
gather_facts: true
tags: [cacti]
tasks:
- name: Import EPEL GPG key
rpm_key:
key: http://dl.fedoraproject.org/pub/epel/RPM-GPG-KEY-EPEL-6
state: present
- name: Add YUM repo
yum_repository:
name: epel
description: EPEL YUM repo
baseurl: https://dl.fedoraproject.org/pub/epel/$releasever/$basearch/
gpgcheck: yes
- name: Update the software package repository
yum:
name: '*'
update_cache: yes
- name: Install cacti
package:
name: "{{ item }}"
state: latest
with_items:
- cacti
- name: Create cacti database user
mysql_user:
name: cacti
password: "{{ mysql_cacti_password_hash }}"
encrypted: yes
priv: '*.*:ALL,GRANT'
state: presentCode language: JavaScript (javascript)
Play 4: Apply MySQL Timezone Bug Fix
MySQL 5.1.73-8 (the version shipped with CentOS 6) lacks timezone data in its system tables, which causes Cacti to report an error. To resolve this, the mysql_test_data_timezone.sql file must be imported into the mysql database, and the cacti user must be granted SELECT access to the time_zone_name table.
This fix is tracked in the Cacti project as issue #242. Isolating it in its own play (or separate playbook) makes it easy to skip once you upgrade to a MySQL version where the bug no longer applies.
- name: For bug https://github.com/Cacti/cacti/issues/242
hosts: centos
become: yes
become_method: sudo
gather_facts: true
tags: [bug]
tasks:
- name: Import mysql_test_data_timezone.sql
mysql_db:
state: import
name: mysql
target: /usr/share/mysql/mysql_test_data_timezone.sql
- name: Grant privileges
mysql_user:
name: cacti
append_privs: true
priv: 'mysql.time_zone_name:SELECT'
state: presentCode language: JavaScript (javascript)
Play 5: Configure Cacti
The final play ties everything together:
- Creates the
cactidatabase and imports the Cacti schema (cacti.sql) - Updates database credentials in
/etc/cacti/db.phpusing thelineinfilemodule - Opens port 80 in iptables
- Updates
/etc/httpd/conf.d/cacti.confto allow access from all hosts (adjust this for production) - Enables the Cacti cron poller by uncommenting its entry in
/etc/cron.d/cacti - Restarts both MySQL and Apache, then waits for each to become available
- name: Configuration
hosts: centos
become: yes
become_method: sudo
gather_facts: true
tags: [config]
tasks:
- name: Create a database for cacti
mysql_db:
name: cacti
state: present
- name: Import cacti.sql
mysql_db:
state: import
name: cacti
target: /usr/share/doc/cacti-1.0.4/cacti.sql
- name: Update database credentials in config file
lineinfile:
dest: /etc/cacti/db.php
regexp: "{{ item.regexp }}"
line: "{{ item.line }}"
with_items:
- { regexp: '^\$database_username', line: "$database_username = '{{ mysql_username }}';" }
- { regexp: '^\$database_password', line: "$database_password = '{{ mysql_password }}';" }
- name: Allow port 80
shell: iptables -I INPUT 5 -p tcp --dport 80 -m state --state NEW,ESTABLISHED -j ACCEPT
- name: Update access in cacti.conf for httpd
replace:
dest: /etc/httpd/conf.d/cacti.conf
regexp: "{{ item.regexp }}"
replace: "{{ item.replace }}"
with_items:
- { regexp: 'Require host localhost', replace: 'Require all granted' }
- { regexp: 'Allow from localhost', replace: 'Allow from all' }
- lineinfile:
dest: /etc/cron.d/cacti
regexp: '^#(.*)$'
line: '\1'
backrefs: yes
- name: Restart mysqld server
service:
name: mysqld
state: restarted
- wait_for:
port: 3306
- name: Restart the httpd server
service:
name: httpd
state: restarted
- wait_for:
port: 80Code language: JavaScript (javascript)
The Cacti cron poller runs every five minutes to collect device metrics:
*/5 * * * * cacti /usr/bin/php /usr/share/cacti/poller.php > /dev/null 2>&1Code language: JavaScript (javascript)

Running the Full Playbook
Once all plays are defined in cacti.yml, run the complete deployment with a single command. The --ask-vault-pass flag prompts for the Ansible Vault password so that encrypted credentials can be decrypted at runtime:
$ ansible-playbook -i inventory/kvm/inventory playbooks/configuration/cacti.yml --ask-vault-pass
After the playbook completes successfully, navigate to http://<VM_IP>/cacti in a browser to run the Cacti web installer. The default login credentials are admin / admin; you will be prompted to change the password immediately after first login.
Key Takeaways
- Ansible Vault keeps database credentials out of source control while still making them available to playbooks at runtime.
- Tagging plays (
httpd,database-web,cacti,bug,config) allows you to re-run or skip individual stages during troubleshooting or upgrades. - Isolating bug-fix plays makes it easy to remove them when you upgrade to a version of MySQL or Cacti where the fix is no longer needed.
- The
wait_formodule prevents subsequent tasks from executing before a service is actually ready to accept connections, making the playbook more reliable than simple service start tasks alone. - For production use, replace the broad
Require all granted/Allow from allApache directives with IP-restricted access controls, re-enable SELinux, and use firewalld or a dedicated firewall role instead of rawiptablesshell commands.