Configure ClamAV cluster scanning for high availability and enterprise threat detection

Advanced 45 min Apr 06, 2026 248 views
Ubuntu 24.04 Debian 12 AlmaLinux 9 Rocky Linux 9

Build a production-grade ClamAV cluster with HAProxy load balancing, shared virus definitions, and comprehensive monitoring for enterprise-scale threat detection and high availability.

Prerequisites

  • Root or sudo access
  • Multiple servers (minimum 3 nodes)
  • NFS or shared storage system
  • Basic understanding of load balancing
  • Network connectivity between cluster nodes

What this solves

Enterprise environments require robust antivirus scanning that can handle high volumes of files without creating single points of failure. A ClamAV cluster provides distributed scanning capabilities, automatic failover, and scalable threat detection across multiple nodes.

This setup ensures continuous malware protection even when individual scanner nodes fail, while distributing the computational load across multiple servers for improved performance and reliability.

Step-by-step configuration

Update system packages

Start by updating your package manager to ensure you get the latest versions of ClamAV and dependencies.

sudo apt update && sudo apt upgrade -y
sudo dnf update -y

Install ClamAV and HAProxy

Install ClamAV daemon, scanner tools, and HAProxy for load balancing across all cluster nodes.

sudo apt install -y clamav clamav-daemon clamav-freshclam haproxy rsync nfs-common
sudo dnf install -y clamav clamav-server clamav-update haproxy rsync nfs-utils

Create ClamAV system users and directories

Set up dedicated users and secure directory structure for the ClamAV cluster. Each node needs consistent user permissions for shared storage access.

sudo useradd -r -s /bin/false -d /var/lib/clamav clamav
sudo mkdir -p /var/lib/clamav/shared
sudo mkdir -p /var/log/clamav
sudo mkdir -p /run/clamav
sudo chown -R clamav:clamav /var/lib/clamav /var/log/clamav /run/clamav
sudo chmod 755 /var/lib/clamav /var/log/clamav
sudo chmod 750 /run/clamav

Configure shared virus definition storage

Set up NFS or shared storage for virus definitions. This ensures all cluster nodes use the same signature database and reduces update overhead.

sudo mkdir -p /mnt/clamav-shared
sudo chown clamav:clamav /mnt/clamav-shared
sudo chmod 755 /mnt/clamav-shared

Add the shared storage mount to fstab for automatic mounting:

203.0.113.10:/shared/clamav /mnt/clamav-shared nfs rw,hard,intr 0 0
sudo mount /mnt/clamav-shared

Configure ClamAV daemon for cluster operation

Configure each ClamAV node to use shared virus definitions and accept network connections for distributed scanning.

# Network configuration
TCPSocket 3310
TCPAddr 0.0.0.0
MaxConnectionQueueLength 100
MaxThreads 20
ReadTimeout 300
CommandReadTimeout 30
SendBufTimeout 500
MaxQueue 500

Database configuration

DatabaseDirectory /mnt/clamav-shared/db TemporaryDirectory /tmp DatabaseOwner clamav

Logging

LogFile /var/log/clamav/clamav.log LogTime yes LogClean no LogSyslog yes LogFacility LOG_DAEMON LogVerbose yes

Security settings

User clamav AllowAllMatchScan yes Foreground no PidFile /run/clamav/clamd.pid LocalSocket /run/clamav/clamd.ctl LocalSocketGroup clamav LocalSocketMode 666

Performance tuning

MaxScanSize 500M MaxFileSize 100M MaxRecursion 20 MaxFiles 15000 MaxEmbeddedPE 40M MaxHTMLNormalize 40M MaxHTMLNoTags 8M MaxScriptNormalize 5M MaxZipTypeRcg 1M

Archive scanning

ScanArchive yes ScanPDF yes ScanSWF yes ScanXMLDOCS yes ScanHWP3 yes ScanOLE2 yes ScanPE yes ScanELF yes ScanMail yes ScanPartialMessages yes ScanHTML yes

Exclude common false positives

ExcludePath ^/proc/ ExcludePath ^/sys/ ExcludePath ^/dev/ ExcludePath ^/run/user/

Configure FreshClam for shared updates

Set up virus definition updates to use shared storage. Only one node should update definitions to prevent conflicts.

# Database directory
DatabaseDirectory /mnt/clamav-shared/db
DatabaseOwner clamav

Update settings

DNSDatabaseInfo current.cvd.clamav.net DatabaseMirror db.local.clamav.net DatabaseMirror database.clamav.net

Logging

UpdateLogFile /var/log/clamav/freshclam.log LogFileMaxSize 20M LogTime yes LogVerbose yes LogSyslog yes LogFacility LOG_DAEMON

Network settings

ReceiveTimeout 60 TestDatabases yes ScriptedUpdates yes CompressLocalDatabase no

Security

Bytecode yes

Primary update node configuration (only on one node)

OnUpdateExecute /usr/bin/systemctl reload clamav-daemon

Secondary nodes should not update

Comment out the update mirrors on secondary nodes

and rely on shared storage synchronization

Create database synchronization script

Create a script for secondary nodes to synchronize with the primary update node and reload the daemon when definitions change.

#!/bin/bash

ClamAV Database Synchronization Script

Run on secondary nodes to sync with primary

PRIMARY_NODE="203.0.113.10" SHARED_DB_PATH="/mnt/clamav-shared/db" LOCAL_DB_PATH="/var/lib/clamav" LOG_FILE="/var/log/clamav/sync.log"

Create log entry

echo "$(date): Starting ClamAV database sync" >> $LOG_FILE

Check if shared storage is mounted

if ! mountpoint -q /mnt/clamav-shared; then echo "$(date): ERROR - Shared storage not mounted" >> $LOG_FILE exit 1 fi

Check for database updates

if [ -f "$SHARED_DB_PATH/main.cvd" ] && [ -f "$SHARED_DB_PATH/daily.cvd" ]; then # Compare timestamps SHARED_TIME=$(stat -c %Y "$SHARED_DB_PATH/main.cvd") LOCAL_TIME=0 if [ -f "$LOCAL_DB_PATH/main.cvd" ]; then LOCAL_TIME=$(stat -c %Y "$LOCAL_DB_PATH/main.cvd") fi if [ $SHARED_TIME -gt $LOCAL_TIME ]; then echo "$(date): Updating local database from shared storage" >> $LOG_FILE # Stop daemon during update systemctl stop clamav-daemon # Sync databases rsync -av --delete "$SHARED_DB_PATH/" "$LOCAL_DB_PATH/" chown -R clamav:clamav "$LOCAL_DB_PATH" # Restart daemon systemctl start clamav-daemon echo "$(date): Database sync completed successfully" >> $LOG_FILE else echo "$(date): Local database is up to date" >> $LOG_FILE fi else echo "$(date): ERROR - Shared databases not found" >> $LOG_FILE fi
sudo chmod 755 /usr/local/bin/clamav-sync.sh
sudo chown root:root /usr/local/bin/clamav-sync.sh

Configure HAProxy load balancer

Set up HAProxy to distribute scanning requests across cluster nodes with health checks and automatic failover.

global
    daemon
    user haproxy
    group haproxy
    pidfile /var/run/haproxy.pid
    log stdout local0 info
    chroot /var/lib/haproxy
    stats socket /run/haproxy/admin.sock mode 660 level admin
    stats timeout 30s
    
    # SSL/TLS settings
    ssl-default-bind-ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384
    ssl-default-bind-options no-sslv3 no-tlsv10 no-tlsv11

defaults
    mode tcp
    timeout connect 5000ms
    timeout client 50000ms
    timeout server 50000ms
    log global
    option tcplog
    option dontlognull
    retries 3
    option redispatch
    maxconn 2000

Statistics interface

frontend stats bind *:8404 mode http stats enable stats uri /stats stats refresh 30s stats admin if TRUE stats auth admin:clamav-cluster-2024

ClamAV cluster frontend

frontend clamav-cluster bind *:3310 mode tcp option tcplog timeout client 300000ms default_backend clamav-nodes

ClamAV backend nodes

backend clamav-nodes mode tcp balance roundrobin timeout server 300000ms timeout connect 10000ms # Health check configuration option tcp-check tcp-check send "PING\n" tcp-check expect string "PONG" # Cluster nodes server clamav-node1 203.0.113.11:3310 check inter 10s rise 3 fall 3 maxconn 50 server clamav-node2 203.0.113.12:3310 check inter 10s rise 3 fall 3 maxconn 50 server clamav-node3 203.0.113.13:3310 check inter 10s rise 3 fall 3 maxconn 50

Create systemd services and timers

Set up systemd services for automatic database synchronization and cluster health monitoring.

[Unit]
Description=ClamAV Database Synchronization
After=network.target
Requires=network.target

[Service]
Type=oneshot
User=root
ExecStart=/usr/local/bin/clamav-sync.sh
StandardOutput=journal
StandardError=journal
[Unit]
Description=Run ClamAV database sync every 30 minutes
Requires=clamav-sync.service

[Timer]
OnCalendar=*:0/30
Persistent=true

[Install]
WantedBy=timers.target
sudo systemctl daemon-reload
sudo systemctl enable clamav-sync.timer
sudo systemctl start clamav-sync.timer

Configure firewall rules

Open necessary ports for ClamAV cluster communication and HAProxy management interface.

sudo ufw allow 3310/tcp comment "ClamAV daemon"
sudo ufw allow 8404/tcp comment "HAProxy stats"
sudo ufw allow from 203.0.113.0/24 to any port 2049 comment "NFS for cluster"
sudo ufw reload
sudo firewall-cmd --permanent --add-port=3310/tcp
sudo firewall-cmd --permanent --add-port=8404/tcp
sudo firewall-cmd --permanent --add-rich-rule="rule family='ipv4' source address='203.0.113.0/24' port protocol='tcp' port='2049' accept"
sudo firewall-cmd --reload

Initialize virus definitions

Download initial virus definitions on the primary node and set up the shared database structure.

sudo mkdir -p /mnt/clamav-shared/db
sudo chown -R clamav:clamav /mnt/clamav-shared/db
sudo -u clamav freshclam --config-file=/etc/clamav/freshclam.conf

Start and enable services

Enable and start ClamAV daemon and HAProxy on all cluster nodes.

sudo systemctl enable clamav-daemon clamav-freshclam haproxy
sudo systemctl start clamav-daemon clamav-freshclam haproxy
sudo systemctl status clamav-daemon haproxy

Configure monitoring and alerting

Create cluster health check script

Set up monitoring script to check cluster node health and send alerts when nodes become unavailable.

#!/bin/bash

ClamAV Cluster Health Monitoring Script

CLUSTER_NODES=("203.0.113.11" "203.0.113.12" "203.0.113.13") LOG_FILE="/var/log/clamav/cluster-health.log" ALERT_EMAIL="admin@example.com" MIN_HEALTHY_NODES=2 healthy_nodes=0 failed_nodes=() echo "$(date): Starting cluster health check" >> $LOG_FILE for node in "${CLUSTER_NODES[@]}"; do if timeout 10 echo "PING" | nc -w 5 $node 3310 | grep -q "PONG"; then echo "$(date): Node $node is healthy" >> $LOG_FILE ((healthy_nodes++)) else echo "$(date): Node $node is DOWN" >> $LOG_FILE failed_nodes+=("$node") fi done echo "$(date): Healthy nodes: $healthy_nodes/${#CLUSTER_NODES[@]}" >> $LOG_FILE if [ $healthy_nodes -lt $MIN_HEALTHY_NODES ]; then ALERT_MSG="CRITICAL: ClamAV cluster has only $healthy_nodes healthy nodes out of ${#CLUSTER_NODES[@]}. Failed nodes: ${failed_nodes[*]}" echo "$(date): $ALERT_MSG" >> $LOG_FILE echo "$ALERT_MSG" | mail -s "ClamAV Cluster Alert" $ALERT_EMAIL fi
sudo chmod 755 /usr/local/bin/clamav-monitor.sh

Set up log rotation

Configure log rotation to prevent cluster logs from consuming excessive disk space.

/var/log/clamav/*.log {
    weekly
    rotate 12
    compress
    delaycompress
    missingok
    notifempty
    postrotate
        /bin/systemctl reload clamav-daemon > /dev/null 2>&1 || true
    endscript
}

Verify your setup

Test the ClamAV cluster configuration and verify load balancing is working correctly.

# Check cluster node status
sudo systemctl status clamav-daemon haproxy

Test direct node connectivity

echo "PING" | nc -w 5 203.0.113.11 3310 echo "PING" | nc -w 5 203.0.113.12 3310

Test load balancer

echo "PING" | nc -w 5 localhost 3310

Check HAProxy statistics

curl -u admin:clamav-cluster-2024 http://localhost:8404/stats

Test file scanning through cluster

echo "X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*" > /tmp/eicar.txt clamdscan --fdpass /tmp/eicar.txt

Monitor cluster health

sudo /usr/local/bin/clamav-monitor.sh

Check shared database synchronization

ls -la /mnt/clamav-shared/db/ sudo systemctl status clamav-sync.timer

You can also integrate this ClamAV cluster with your existing monitoring infrastructure using techniques from our Linux system monitoring tutorial or set up centralized logging with ELK Stack configuration.

Common issues

Symptom Cause Fix
Nodes can't access shared storage NFS mount permission issues sudo mount -t nfs -o rw,hard,intr server:/path /mnt/clamav-shared
ClamAV daemon fails to start Database directory permissions sudo chown -R clamav:clamav /var/lib/clamav and sudo chmod 755 /var/lib/clamav
HAProxy shows nodes as down Health check timeout Increase timeout values in haproxy.cfg backend section
Database sync fails Network connectivity issues Check NFS connectivity and firewall rules for port 2049
High memory usage on nodes Too many concurrent scans Reduce MaxThreads in clamd.conf and adjust HAProxy maxconn
Virus definitions out of date FreshClam update failures Check network connectivity and DNS resolution for clamav.net
Never use chmod 777. It gives every user on the system full access to your files. Instead, fix ownership with chown and use minimal permissions like 755 for directories and 644 for files.

Next steps

Automated install script

Run this to automate the entire setup

Need help?

Don't want to manage this yourself?

We handle infrastructure security hardening for businesses that depend on uptime. From initial setup to ongoing operations.