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
sudoprivileges. - At least 2 GB of RAM. The
clamddaemon keeps the full signature database in memory and uses around 1 to 1.5 GB on its own. - Outbound HTTPS access, so
freshclamcan 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:
| Service | Purpose |
|---|---|
clamav-freshclam | Downloads signature updates, by default up to 24 times per day |
clamav-daemon | Runs 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:
clamscanis standalone. It loads the whole database every time it runs, which takes time and memory, but it needs no daemon.clamdscansends the files to the runningclamd, 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
--multiscanscans several files in parallel using the daemon's worker threads.--infected(-i) prints only infected files instead of oneOKline 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.
WarningAvoid
--removeuntil you trust the results on your data. A false positive would delete a legitimate file. Review detections first, or use--move=/path/to/quarantineto set suspicious files aside.
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.
