Cacti is a web-based network graphing solution that uses SNMP to poll network devices and RRDtool for data storage. As a system that handles SNMP credentials and stores sensitive network topology data, proper security hardening is essential. This guide covers security measures for production Cacti deployments.
Cacti architecture includes these security-sensitive components:
Key security concerns include web application security, SNMP credential protection, SQL injection prevention, and securing the polling infrastructure.
Configure firewall rules for Cacti:
# Cacti Web Interface (Apache/Nginx)
ufw allow from 10.0.0.0/8 to any port 80 proto tcp
ufw allow from 10.0.0.0/8 to any port 443 proto tcp
# SNMP (for polling devices)
ufw allow out to any port 161 proto udp
ufw allow out to any port 162 proto udp # SNMP traps
# MySQL (restrict to localhost)
ufw allow from 127.0.0.1 to any port 3306 proto tcp
# Block external access
ufw deny from any to any port 3306 proto tcp
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: cacti-network-policy
spec:
podSelector:
matchLabels:
app: cacti
ingress:
- from:
- namespaceSelector:
matchLabels:
name: monitoring
ports:
- protocol: TCP
port: 80
- protocol: TCP
port: 443
egress:
- to:
- namespaceSelector: {}
ports:
- protocol: UDP
port: 161
Configure web server binding:
# /etc/apache2/sites-available/cacti.conf
<VirtualHost 10.0.1.100:443>
ServerName cacti.company.com
DocumentRoot /var/www/html/cacti
SSLEngine on
SSLCertificateFile /etc/ssl/certs/cacti.crt
SSLCertificateKeyFile /etc/ssl/private/cacti.key
<Directory /var/www/html/cacti>
Require ip 10.0.0.0/8 192.168.0.0/16
Options -Indexes
AllowOverride None
</Directory>
</VirtualHost>
Configure poller binding:
// /var/www/html/cacti/config.php
$database_type = 'mysql';
$database_default = 'cacti';
$database_hostname = 'localhost';
$database_port = '3306';
$database_retries = 3;
$database_ssl = false; // Enable for TLS
Configure Cacti user authentication:
// Cacti Web UI: Configuration → Settings → Authentication
// Authentication Method: LDAP or Built In
// Built-in password policy
// Minimum password length: 12
// Require mixed case: Yes
// Require numbers: Yes
// Require special characters: Yes
// Password expiration: 90 days
Configure Cacti user domains and permissions:
User Permission Levels:
- Normal User: View graphs only
- General Administration: Manage devices and graphs
- Site Administration: Full access including user management
Configure in Cacti UI:
Configure LDAP authentication:
// /var/www/html/cacti/config.php
$ldap_enabled = true;
$ldap_server = 'ldap.company.com';
$ldap_port = 636;
$ldap_version = 3;
$ldap_encryption = 'ssl';
$ldap_dn = 'cn=admin,dc=company,dc=com';
$ldap_password = '${LDAP_PASSWORD}';
$ldap_basedn = 'dc=company,dc=com';
$ldap_filter = '(objectClass=inetOrgPerson)';
$ldap_mode = 1; // Search and bind
$ldap_grouping = 0; // Disable or configure group DN
Configure AD authentication:
// /var/www/html/cacti/config.php
$ldap_enabled = true;
$ldap_server = 'dc.company.com';
$ldap_port = 636;
$ldap_version = 3;
$ldap_encryption = 'ssl';
$ldap_dn = 'cn=cacti,ou=services,dc=company,dc=com';
$ldap_password = '${AD_PASSWORD}';
$ldap_basedn = 'dc=company,dc=com';
$ldap_filter = '(&(objectClass=user)(objectCategory=person))';
$ldap_mode = 1;
$ldap_grouping = 1;
$ldap_groupdn = 'CN=Cacti-Users,OU=Groups,DC=company,DC=com';
Enable two-factor authentication:
// Install TOTP plugin
// Console → Plugin Management → Install TOTP plugin
// Configure in:
// Console → Configuration → Settings → Authentication
// Enable Two-Factor Authentication: Yes
// Required for administrators: Yes
Configure HTTPS for Cacti:
# /etc/apache2/sites-available/cacti-ssl.conf
<VirtualHost *:443>
ServerName cacti.company.com
DocumentRoot /var/www/html/cacti
SSLEngine on
SSLCertificateFile /etc/ssl/certs/cacti.crt
SSLCertificateKeyFile /etc/ssl/private/cacti.key
SSLCertificateChainFile /etc/ssl/certs/ca-bundle.crt
SSLProtocol all -SSLv2 -SSLv3 -TLSv1 -TLSv1.1
SSLCipherSuite ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256
SSLHonorCipherOrder on
# Security headers
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
Header always set X-Frame-Options "SAMEORIGIN"
Header always set X-Content-Type-Options "nosniff"
Header always set X-XSS-Protection "1; mode=block"
Header always set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src * data:; font-src 'self' data:; connect-src 'self'"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
<Directory /var/www/html/cacti>
Options -Indexes
AllowOverride None
Require all granted
</Directory>
</VirtualHost>
Configure TLS for MySQL:
// /var/www/html/cacti/config.php
$database_ssl = true;
$database_ssl_key = '/etc/mysql/client.key';
$database_ssl_cert = '/etc/mysql/client.crt';
$database_ssl_ca = '/etc/mysql/ca.crt';
Configure MySQL for TLS:
-- Require SSL for Cacti user
ALTER USER 'cacti'@'localhost' REQUIRE SSL;
FLUSH PRIVILEGES;
Use SNMPv3 for secure device polling:
Configure in Cacti UI:
- Console → Devices → [Select Device] → Edit
- SNMP Version: Version 3
- Security Level: AuthPriv
- Username: cacti-poller
- Authentication Protocol: SHA
- Authentication Password: ${SNMP_AUTH_PASSWORD}
- Privacy Protocol: AES
- Privacy Password: ${SNMP_PRIV_PASSWORD}
Secure Cacti web application:
// /var/www/html/cacti/config.php
// Session security
$session_lifetime = 3600;
$session_secure = true;
$session_httponly = true;
// CSRF protection (built-in, ensure enabled)
// Input validation (built-in)
Configure PHP security:
# /etc/php/8.1/apache2/php.ini
expose_php = Off
display_errors = Off
log_errors = On
error_log = /var/log/php/error.log
session.cookie_secure = 1
session.cookie_httponly = 1
session.cookie_samesite = Strict
session.use_strict_mode = 1
# Disable dangerous functions
disable_functions = exec,passthru,shell_exec,system,proc_open,popen,curl_exec,curl_multi_exec,parse_ini_file,show_source
Secure Cacti API access:
| Endpoint | Risk Level | Access Control |
|---|---|---|
GET /host.php |
Low | Authenticated users |
GET /graph.php |
Low | Authenticated users |
POST /host.php?action=edit |
High | Admin only |
POST /data_input.php |
High | Admin only |
GET /user_admin.php |
High | Site Admin only |
POST /user_admin.php |
Critical | Site Admin only |
Secure poller execution:
# Set restrictive permissions on poller
chown root:cacti /var/www/html/cacti/poller.php
chmod 750 /var/www/html/cacti/poller.php
# Run poller as cacti user
# /etc/cron.d/cacti
*/5 * * * * cacti /usr/bin/php /var/www/html/cacti/poller.php > /dev/null 2>&1
Protect SNMP credentials:
// Cacti stores SNMP credentials encrypted in database
// Ensure encryption is enabled:
// Console → Configuration → Settings → General
// Encryption Key: ${ENCRYPTION_KEY} (32 characters)
// The key is stored in:
// /var/www/html/cacti/include/config.php
Secure Cacti database:
-- Create dedicated database user
CREATE USER 'cacti'@'localhost' IDENTIFIED BY 'strong_password';
GRANT SELECT, INSERT, UPDATE, DELETE, INDEX, ALTER, CREATE, DROP, REFERENCES, CREATE TEMPORARY TABLES, LOCK TABLES ON cacti.* TO 'cacti'@'localhost';
FLUSH PRIVILEGES;
-- Enable SSL requirement
ALTER USER 'cacti'@'localhost' REQUIRE SSL;
-- Remove anonymous users
DELETE FROM mysql.user WHERE User='';
DELETE FROM mysql.user WHERE User='root' AND Host NOT IN ('localhost', '127.0.0.1', '::1');
Implement database encryption:
-- Enable TDE (MySQL Enterprise)
ALTER TABLE host ENCRYPTION='Y';
ALTER TABLE graph_local ENCRYPTION='Y';
ALTER TABLE data_local ENCRYPTION='Y';
Secure RRD files:
# Set restrictive permissions
chown -R cacti:cacti /var/www/html/cacti/rra
chmod -R 750 /var/www/html/cacti/rra
# Use encrypted filesystem
# Mount /var/www/html/cacti/rra on encrypted volume
Secure sensitive configuration:
// /var/www/html/cacti/include/config.php
// Use environment variables
$database_username = getenv('CACTI_DB_USER');
$database_password = getenv('CACTI_DB_PASSWORD');
// Or use external secrets file
if (file_exists('/etc/cacti/secrets.php')) {
include '/etc/cacti/secrets.php';
}
Protect secrets file:
# Set restrictive permissions
chown root:www-data /etc/cacti/secrets.php
chmod 640 /etc/cacti/secrets.php
Secure Cacti backups:
#!/bin/bash
# Secure backup script
BACKUP_DIR="/secure/backups/cacti"
DATE=$(date +%Y%m%d)
# Database backup
mysqldump --single-transaction cacti > ${BACKUP_DIR}/cacti-db-${DATE}.sql
# RRD files backup
tar -czf ${BACKUP_DIR}/cacti-rra-${DATE}.tar.gz /var/www/html/cacti/rra
# Encrypt backups
gpg -e --recipient security@company.com ${BACKUP_DIR}/cacti-db-${DATE}.sql
gpg -e --recipient security@company.com ${BACKUP_DIR}/cacti-rra-${DATE}.tar.gz
# Set restrictive permissions
chmod 600 ${BACKUP_DIR}/cacti-*.gpg
# Remove unencrypted backups
rm ${BACKUP_DIR}/cacti-db-${DATE}.sql
rm ${BACKUP_DIR}/cacti-rra-${DATE}.tar.gz
Enable audit logging:
Cacti logs actions in:
- Console → Configuration → Settings → General → Log Settings
- Log file: /var/www/html/cacti/log/cacti.log
Enable logging for:
- User authentication
- Configuration changes
- Device modifications
- Graph changes
Configure web server access logging:
# /etc/apache2/sites-available/cacti.conf
CustomLog /var/log/apache2/cacti_access.log combined
ErrorLog /var/log/apache2/cacti_error.log
Monitor Cacti for security events:
#!/bin/bash
# /usr/local/bin/check-cacti-security.sh
# Check for failed login attempts
FAILED_LOGINS=$(grep -c "LOGIN FAILED" /var/www/html/cacti/log/cacti.log 2>/dev/null || echo 0)
if [ "$FAILED_LOGINS" -gt 10 ]; then
echo "CRITICAL: Multiple failed login attempts"
exit 2
fi
# Check for configuration changes
CONFIG_CHANGES=$(grep -c "CONFIG" /var/www/html/cacti/log/cacti.log 2>/dev/null || echo 0)
if [ "$CONFIG_CHANGES" -gt 50 ]; then
echo "WARNING: High number of configuration changes"
exit 1
fi
Forward logs to SIEM:
# /etc/rsyslog.d/cacti.conf
/var/www/html/cacti/log/cacti.log @siem.company.com:514