Configure ClamAV cluster scanning for high availability and enterprise threat detection

Advanced 45 min Apr 06, 2026 1,153 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
</code><h2><code>and rely on shared storage synchronization</code></h2></pre>
</div>

<div class="step">
### Create database synchronization script
<p>Create a script for secondary nodes to synchronize with the primary update node and reload the daemon when definitions change.</p>
<pre class="terminal"><code>#!/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

Don't want to manage this yourself?

We handle infrastructure for businesses that depend on uptime. Fully managed, with one fixed contact who knows your setup.

You get one fixed contact who knows your setup

Rotterdam 01:28 · reachable in a message, no ticket form