"You do not need antivirus on Linux" is a widespread opinion, and in its original form it is correct: viruses that infect Linux system files barely exist. ClamAV on a server has a different job, and it is stated like this: find what was uploaded through your site. Web shells above all.

What exactly we are looking for

A web shell is a small PHP file that makes it possible to run commands through a browser. It arrives through an upload form, a vulnerable plugin or a guessed administrator password, settles among the pictures in an uploads directory and lives there for months. Neither the firewall nor Fail2ban will see it: requests to it look like ordinary requests to the site.

Besides that, ClamAV finds:

  • malicious files uploaded by users and served onwards from your server — that is usually what the letter from your provider is about;
  • mail attachments, if the server handles mail;
  • Windows malware in file storage — the server itself is not interested, but the employee who downloads the file will suffer.

About the limits, straight away: ClamAV does not prevent a break-in and does not detect one by itself. It detects traces. A subtle backdoor appended to an existing theme file will be missed by a signature scanner — that is what file integrity monitoring is for.

Installation and the one setting that matters

sudo apt install clamav clamav-daemon
sudo systemctl status clamav-freshclam

The second command matters more than the first. freshclam is the signature update service, and if it is not running, ClamAV becomes useless while continuing to report successful scans. Check the age of the databases like this:

sudo ls -l /var/lib/clamav/*.c?d
sudo freshclam

Databases older than a week mean the mechanism is broken. A frequent cause is blocked outbound connections, or freshclam having been run manually as root, after which the service cannot write to its own file because of the permissions.

Memory: daemon or one-off run

There are two ways to use it, and the choice should be deliberate.

clamd is a permanently running daemon holding the databases in memory. It answers quickly but occupies about a gigabyte of RAM. On a VPS with one or two gigabytes that is unacceptable: the daemon will squeeze out the database and PHP-FPM, and the server will start crawling for no visible reason. Diagnosed with free -m and by active swap.

The alternative is clamscan on a schedule. It is slower (it loads the databases on every run, about a minute) but only works while scanning:

sudo clamscan -r -i --exclude-dir='^/(proc|sys|dev|run)' /var/www

The -i flag prints only what was found; otherwise the output runs to hundreds of thousands of lines.

Scan directories, not the disk

A full disk scan on a weak server takes hours and creates load, which is why it gets scheduled at night and then switched off altogether. It is more practical to scan what actually changes from outside:

0 3 * * * ionice -c3 nice -n19 clamscan -r -i --move=/var/quarantine /var/www/uploads

Three things in that line matter. ionice and nice remove the impact on the site. --move rather than --remove — the file goes to quarantine instead of being deleted: false positives happen, and a customer's file deleted by mistake cannot be recovered from anywhere. And the directory to scan is the one files are uploaded into, not the root.

Signatures for web shells

The stock ClamAV databases are weak on PHP backdoors — they are aimed mostly at mail traffic. Linux Malware Detect (maldet) does noticeably better here: it has its own signature set built precisely around web shells, and it scans with the ClamAV engine if that is installed. The usual combination is ClamAV as the engine and updatable database, maldet as the source of specialised signatures.

What to do with a find

A web shell found is not the end of the investigation but its beginning. A file in an uploads directory means somebody was able to write there, and deleting the file does not remove that ability.

  1. Do not delete it immediately — keep a copy and note the file's modification time.
  2. Using that time, find in the web server logs which request put it there. That is the vulnerability.
  3. Look for neighbours: a backdoor is almost never left in a single copy.
  4. Check what else on disk changed in the same period, and whether new cron jobs and SSH keys have appeared.

A separate word about false positives. Template libraries and minified JavaScript sometimes match signatures. Hence quarantine rather than deletion, and hence the advice not to attach every third-party signature set you can find: trust in a report where half the lines are noise is lost within a week, and people stop reading it.

The practical value of a scanner is in its results catching your eye: when the last scan ran, how old the databases are, what was found. That is a single line, but its absence turns an installed antivirus into a tick in a box. What that line looks like is on the demo page below.