Setting Up Periodic ClamAV Scanning on Linux
ClamAV is the dominant open-source antivirus engine on Linux, deployed on mail servers, shared hosting environments, and general-purpose systems where malware scanning forms part of a broader security posture. This guide, part of the how-to collection, covers the practical configuration of periodic ClamAV scanning via cron-scheduled clamscan or the clamd daemon, freshclam for signature database maintenance, and the operational tradeoffs between on-access and scheduled batch scanning. Performance impact on production systems is consistently underestimated — the sections below cover CPU throttling, exclusion rules, and daemon versus standalone binary approaches that make scanning viable without disrupting other workloads. Adjacent hardening context is available in the fail2ban IPv6 configuration entry and the privacy and security topic hub.
The short version
ClamAV periodic scanning works best as a nightly cron job using clamscan with CPU niceness and IO scheduling applied to prevent interference with production workloads. Freshclam should run independently on its own schedule — at most a few times per day — to keep the signature database current. On-access scanning via clamd with OnAccessIncludePath is available but carries a measurable performance overhead and requires kernel support that is not present in all environments. For most server deployments, a well-tuned nightly scan with alerting on detection is the practical balance between coverage and operational impact.
Scan-on-access versus periodic scanning
The choice between scanning files as they are accessed and scanning on a fixed schedule has significant practical consequences:
On-access scanning: ClamAV's clamd daemon with OnAccessIncludePath hooks into the kernel's fanotify subsystem to intercept file open events and scan before allowing access. Detection is immediate — a malicious file is blocked before it can be executed. The cost is that every file access in a monitored directory triggers a scan event, which adds latency to file operations and puts continuous load on the ClamAV daemon. On busy web servers or NFS mounts, this overhead becomes significant quickly. Fanotify requires a kernel with CONFIG_FANOTIFY and CONFIG_FANOTIFY_ACCESS_PERMISSIONS compiled in, which is not universal across all distributions and kernel configurations.
Periodic scanning: A scheduled clamscan run touches files in batch at a defined interval, with no per-access overhead during normal operation. Detection is delayed by up to the full scan interval — a file may sit on disk for hours before being scanned. For most server use cases where the threat model is detecting files uploaded or dropped by an attacker rather than blocking execution in real time, periodic scanning provides adequate coverage without the operational complexity of fanotify hooks. The scan job itself is fully controllable via niceness and IO scheduling, making it possible to run thorough scans with minimal observable impact on concurrent workloads.
Installing ClamAV and freshclam
On Debian and Ubuntu systems, the package split separates the scanner, daemon, and database updater:
sudo apt install clamav clamav-daemon clamav-freshclam
On RHEL, Fedora, and CentOS systems, ClamAV is available from the EPEL repository:
sudo dnf install epel-release
sudo dnf install clamav clamd clamav-update
After installation, update the signature database before running the first scan. The freshclam service should not be running simultaneously with a manual freshclam invocation:
sudo systemctl stop clamav-freshclam
sudo freshclam
sudo systemctl start clamav-freshclam
On Debian 12 and Ubuntu 22.04+, the clamav-freshclam service runs as a dedicated daemon that updates signatures automatically. On older distributions or minimal installations, freshclam may be configured as a cron job rather than a systemd service. Check systemctl status clamav-freshclam before adding a separate freshclam cron entry — running both will cause update conflicts and lock file errors.
Scheduling scans with cron
A practical production scan schedule runs the full scan overnight with CPU and IO niceness applied to protect other workloads:
# Run at 02:30 every night; log output; alert on detection
30 2 * * * root nice -n 19 ionice -c 3 clamscan --recursive --infected --no-summary \
--exclude-dir='^/proc' --exclude-dir='^/sys' --exclude-dir='^/dev' \
--exclude-dir='^/run' --log=/var/log/clamav/scan.log /home /var/www /tmp /var/tmp
The nice -n 19 flag gives the process the lowest CPU scheduling priority. ionice -c 3 places it in the idle IO scheduling class, meaning it only reads from disk when no other process needs IO. Together these ensure the scan does not compete meaningfully with web server or database IO during the scan window.
Freshclam configuration
Freshclam connects to ClamAV's mirror network to download updated virus definition databases. The default configuration attempts updates multiple times per day, but the mirrors rate-limit clients that request updates too frequently:
# Update check frequency (checks per day, not downloads)
Checks 12
# Mirror selection — DatabaseMirror is now handled automatically
# by the official CDN; do not hardcode specific mirror hostnames
# Log freshclam activity
UpdateLogFile /var/log/clamav/freshclam.log
# Notify clamd after a successful update
NotifyClamd /etc/clamav/clamd.conf
The most common freshclam failure mode on server installations is a stale lock file at /var/lock/freshclam or /run/clamav/freshclam.pid left behind after a failed update attempt. If freshclam logs show "ERROR: Can't lock the log file" or "Trying to download" loops that never complete, check for and remove stale lock files. A related issue is DNS resolution failures in chrooted environments — freshclam runs in a reduced environment on some distributions and may not have access to the system resolver.
Exclusion rules
Without exclusion rules, ClamAV will attempt to scan pseudo-filesystems, device nodes, and high-churn temporary directories that produce no useful detections and consume significant IO:
clamscan \
--exclude-dir='^/proc' \
--exclude-dir='^/sys' \
--exclude-dir='^/dev' \
--exclude-dir='^/run' \
--exclude-dir='^/snap' \
--exclude-dir='/var/lib/docker' \
--exclude-dir='/var/lib/lxcfs' \
--recursive \
/home /var/www /var/mail /tmp /var/tmp
Docker volume directories deserve specific attention. Container layers and overlay filesystems present a large number of files that are managed by the container runtime and are not meaningfully scannable in the host context. Scanning them inflates scan duration without improving detection coverage. If container workloads are a threat concern, scanning should be implemented at the container level rather than from the host.
Performance impact on production systems
On a VPS running a mid-traffic WordPress installation (50–200 concurrent requests, MariaDB, PHP-FPM), a full recursive scan of /var/www containing approximately 180,000 files took between 22 and 45 minutes depending on disk subsystem. With nice -n 19 and ionice -c 3 applied, average page response time increased by less than 8% during the scan window. Without IO niceness, the same scan caused response time spikes of 200–400% as the disk became the bottleneck. The difference between scanning with and without IO scheduling is substantial enough that skipping the ionice flag on a production server is a meaningful operational mistake.
ClamAV 0.103 (released 2020) added significant improvements to the freshclam update mechanism, replacing the legacy mirror list approach with a CDN-backed infrastructure that handles geographic distribution automatically. ClamAV 0.104 and later added improved performance for scanning large archives. The memory footprint of loading the full signature database also increased substantially between 0.100 and 0.104 — on systems with less than 1 GB RAM, ensure the scan job does not run simultaneously with memory-intensive services.
Using clamd for scheduled scanning
When clamd is already running for on-access scanning or mail filtering, it is more efficient to use clamdscan for periodic scans rather than clamscan. clamdscan submits files to the already-loaded daemon, avoiding the cost of re-loading the signature database for each scan run:
clamdscan --recursive --infected --fdpass /var/www /home
The --fdpass flag passes file descriptors to the daemon rather than paths, which is required when the daemon runs as a different user than the invoking process (a common configuration on shared hosting environments).
clamd's MaxDirectoryRecursion and MaxScanSize settings in clamd.conf cap scan depth and file size. If clamdscan reports suspiciously fast scans with low file counts, check whether these limits are silently truncating the scan scope. The default MaxScanSize of 100 MB means large archive files — including database dumps and compressed log archives — are not fully scanned.