ClamAV is an open-source antivirus engine used on Linux servers to detect malware, web shells, phishing kits and infected uploads. On a server it is most useful for scanning content that comes from outside: user uploads, web roots, mail and shared directories. In this tutorial you will install ClamAV on Ubuntu 24.04, keep its signature database current, run on-demand scans with both clamscan and the faster clamd daemon, and schedule a daily scan with a systemd timer.

Prerequisites

To follow this tutorial you need:

  • A server running Ubuntu 24.04 LTS, for example a CubePath VPS.
  • A non-root user with sudo privileges.
  • At least 2 GB of RAM. The clamd daemon keeps the full signature database in memory and uses around 1 to 1.5 GB on its own.
  • Outbound HTTPS access, so freshclam can download signature updates.

Step 1 - Installing ClamAV

ClamAV is split into several packages. clamav provides the command-line scanner clamscan and the updater freshclam, and clamav-daemon provides clamd plus its client clamdscan:

sudo apt update
sudo apt install clamav clamav-daemon

Check the installed version:

clamscan --version
ClamAV 1.4.3

Once the signature database has been downloaded, the output also shows the database version and date.

The packages install two systemd services:

ServicePurpose
clamav-freshclamDownloads signature updates, by default up to 24 times per day
clamav-daemonRuns clamd, which loads the signatures once and scans files on request

Step 2 - Downloading the signature database

The clamav-freshclam service starts right after installation and downloads the database in the background. The first download is about 300 MB, so give it a minute or two, then check its log:

sudo tail -n 20 /var/log/clamav/freshclam.log
daily.cvd database is up-to-date (version: 27770, sigs: 2074563, f-level: 90, builder: raynman)
main.cvd database is up-to-date (version: 62, sigs: 6647427, f-level: 90, builder: sigmgr)
bytecode.cvd database is up-to-date (version: 335, sigs: 86, f-level: 90, builder: raynman)

The database files live in /var/lib/clamav:

ls -lh /var/lib/clamav
-rw-r--r-- 1 clamav clamav 276K Sep 24 10:20 bytecode.cvd
-rw-r--r-- 1 clamav clamav  60M Sep 24 10:20 daily.cvd
-rw-r--r-- 1 clamav clamav 163M Sep 24 10:21 main.cvd

If you want to force an update by hand, stop the service first. freshclam refuses to run while the service holds its lock file:

sudo systemctl stop clamav-freshclam
sudo freshclam
sudo systemctl start clamav-freshclam

Make sure the updater stays enabled:

sudo systemctl is-enabled clamav-freshclam
enabled

Step 3 - Starting the ClamAV daemon

clamd does not start until the database exists. Now that it does, enable and start the daemon:

sudo systemctl enable --now clamav-daemon

Loading the signatures takes 20 to 60 seconds depending on the CPU. Check the status:

sudo systemctl status clamav-daemon
● clamav-daemon.service - Clam AntiVirus userspace daemon
     Loaded: loaded (/usr/lib/systemd/system/clamav-daemon.service; enabled; preset: enabled)
     Active: active (running) since Thu 2026-09-24 10:24:18 UTC; 45s ago

clamd listens on the local Unix socket /var/run/clamav/clamd.ctl. It does not open any network port, so no firewall change is needed.

Step 4 - Running on-demand scans

There are two ways to scan files, and it helps to know the difference:

  • clamscan is standalone. It loads the whole database every time it runs, which takes time and memory, but it needs no daemon.
  • clamdscan sends the files to the running clamd, which already has the database loaded. Scans start instantly.

Testing detection with the EICAR file

The EICAR test file is a harmless string that every antivirus engine detects on purpose. Create it in /tmp:

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

Scan it with clamscan:

clamscan /tmp/eicar.txt
/tmp/eicar.txt: Win.Test.EICAR_HDB-1 FOUND

----------- SCAN SUMMARY -----------
Known viruses: 8709823
Engine version: 1.4.3
Scanned directories: 0
Scanned files: 1
Infected files: 1
Time: 14.822 sec (0 m 14 s)

Now scan the same file through the daemon:

clamdscan --fdpass /tmp/eicar.txt
/tmp/eicar.txt: Win.Test.EICAR_HDB-1 FOUND

----------- SCAN SUMMARY -----------
Infected files: 1
Time: 0.004 sec (0 m 0 s)

The daemon answers in milliseconds instead of seconds. The --fdpass option passes an open file descriptor to clamd, so the daemon, which runs as the unprivileged clamav user, can scan files it would not otherwise have permission to read.

Remove the test file:

rm /tmp/eicar.txt

Scanning directories

Scan a directory recursively and print only infected files:

sudo clamdscan --fdpass --multiscan --infected /var/www
  • --multiscan scans several files in parallel using the daemon's worker threads.
  • --infected (-i) prints only infected files instead of one OK line per file.

clamdscan is recursive by default. With clamscan, add -r:

sudo clamscan -r -i /home

Both commands return exit code 0 when nothing was found, 1 when at least one file is infected and 2 on errors, which makes them easy to use in scripts.

Step 5 - Tuning the daemon configuration

The daemon's settings live in /etc/clamav/clamd.conf. Open it:

sudo nano /etc/clamav/clamd.conf

A few settings are worth reviewing on a server. Change the existing lines, or add them if they are missing:

# Never descend into virtual filesystems
ExcludePath ^/proc/
ExcludePath ^/sys/
ExcludePath ^/dev/

# Skip very large files that are unlikely to be malware and slow scans down
MaxFileSize 100M
MaxScanSize 400M

# Number of parallel scanning threads
MaxThreads 4

ExcludePath takes a regular expression and can be repeated. Set MaxThreads close to the number of CPU cores you are willing to dedicate to scanning.

Restart the daemon to apply the changes:

sudo systemctl restart clamav-daemon

If the daemon fails to start, the log tells you which line is wrong:

sudo journalctl -u clamav-daemon -n 30 --no-pager

Step 6 - Scheduling a daily scan

A daily scan of the directories that receive external content catches malware that arrived since the last signature update. A systemd timer is a good fit: it logs to the journal and shows a failed state when something is found.

Create a directory for scan reports:

sudo mkdir -p /var/log/clamav/scans

Create the service unit:

sudo nano /etc/systemd/system/clamav-scan.service
[Unit]
Description=Daily ClamAV scan of web and home directories
After=clamav-daemon.service
Requires=clamav-daemon.service

[Service]
Type=oneshot
Nice=10
IOSchedulingClass=idle
ExecStart=/usr/bin/clamdscan --fdpass --multiscan --infected --log=/var/log/clamav/scans/daily.log /var/www /home

Adjust the directories at the end of ExecStart to the paths you want to scan.

Create the timer unit:

sudo nano /etc/systemd/system/clamav-scan.timer
[Unit]
Description=Run the ClamAV scan every night

[Timer]
OnCalendar=*-*-* 03:30:00
RandomizedDelaySec=30m
Persistent=true

[Install]
WantedBy=timers.target

Reload systemd and enable the timer:

sudo systemctl daemon-reload
sudo systemctl enable --now clamav-scan.timer

Check when it will next run:

systemctl list-timers clamav-scan.timer
NEXT                        LEFT     LAST PASSED UNIT              ACTIVATES
Fri 2026-09-25 03:41:12 UTC 17h left -    -      clamav-scan.timer clamav-scan.service

Run the scan once by hand to test it:

sudo systemctl start clamav-scan.service
sudo systemctl status clamav-scan.service

If no infected files are found, the unit ends as inactive (dead) with status=0/SUCCESS. If ClamAV finds something, clamdscan exits with code 1 and the unit appears as failed, which you can spot with:

systemctl --failed

The detections are listed in the report and in the journal:

sudo journalctl -u clamav-scan.service -n 30 --no-pager

Troubleshooting

clamdscan says it cannot connect to clamd. The daemon is not running or is still loading the database. Check sudo systemctl status clamav-daemon. If it keeps stopping, look for out-of-memory kills with sudo journalctl -k | grep -i oom: servers with less than 2 GB of RAM cannot hold the database.

freshclam fails with a lock error. You ran it while the clamav-freshclam service was active. Stop the service before running freshclam by hand, as shown in Step 2.

Permission denied errors in scan results. Add --fdpass to clamdscan and run it with sudo, so the file descriptors are opened with root privileges.

Scans are slow or overload the server. Exclude large archives and backup directories with ExcludePath, lower MaxThreads, and keep scheduled scans limited to directories that receive external content.

Conclusion

ClamAV is now installed with automatic signature updates, a daemon ready for fast scans and a nightly scan of your web and home directories. ClamAV is one layer of defense and works best alongside keeping software patched and limiting who can write to your web root. As next steps, send the scan report to your monitoring or email when the scan unit fails, audit the rest of the system with Lynis, and integrate clamdscan into upload handling in your application.