Scan a Linux server for malware (and what scanners miss)
Malware scanners find known bad files quickly, which makes them a good first check after a suspected compromise. A clean scan does not prove a server is clean, though: rootkits (malware that hides itself from normal tools) can hide from the scanner, and many intrusions use no malware at all. Combine a scan with package verification and a manual check for backdoors.
Do this first.
- If you suspect a compromise, copy logs and suspicious files somewhere safe before scanning; some scanners can move or delete what they flag.
- Never let a scanner delete files automatically on a production server. Use report-only or quarantine mode.
- If the attacker had root, scan from rescue media with the disk mounted read-only, because tools on the running system may be lying to you.
Which tool finds what
| Tool | Good at | Install |
|---|---|---|
| ClamAV | Known malware files of all kinds, mail and uploads | Debian/Ubuntu repositories; EPEL on RHEL-family |
| Linux Malware Detect (LMD) | PHP web shells and injected code on web and shared hosting servers | From the rfxn project (GitHub) |
| rkhunter | Known rootkit files, changed system binaries, hidden files, suspicious settings | Debian/Ubuntu repositories; EPEL |
| chkrootkit | Signs of known rootkits, worms and kernel-module trojans | Debian/Ubuntu repositories |
| YARA | Your own or vendor-published rules for a specific threat | Debian/Ubuntu repositories; EPEL |
| debsums, dpkg -V, rpm -Va | System files that differ from what the package installed | Built in (debsums is a separate package) |
ClamAV
# Debian/Ubuntu: the clamav-freshclam service keeps signatures updated
sudo apt install clamav clamav-daemon
systemctl status clamav-freshclam
# RHEL-family (enable EPEL first; on AlmaLinux and Rocky: sudo dnf install epel-release)
sudo dnf install clamav clamd clamav-update
sudo freshclam
Scan the places malware usually lands, report only infected files, and keep a log. nice and ionice keep the scan from slowing a busy server:
sudo nice -n 19 ionice -c3 clamscan --recursive --infected \
--log=/root/clamscan-$(date +%F).log \
/home /root /tmp /var/tmp /dev/shm /var/www /etc /usr/local
To quarantine instead of just report, add --move=/root/quarantine. The ClamAV manual calls --remove “generally a terrible idea” outside upload directories, because false positives happen. Note that --max-filesize skips larger files and treats them as clean.
Linux Malware Detect
LMD (also called maldet) is designed for threats on shared hosting servers: web shells, injected backdoors and obfuscated PHP. It uses ClamAV as its scan engine when ClamAV is installed. Install it from the project’s repository:
git clone https://github.com/rfxn/linux-malware-detect.git
cd linux-malware-detect
sudo ./install.sh
sudo maldet -u # update signatures
sudo maldet -a /var/www # scan everything under /var/www
sudo maldet -r /var/www 7 # only files changed in the last 7 days
sudo maldet -e list # list scan reports
sudo maldet -e SCANID # view one report
sudo maldet -q SCANID # quarantine the hits from that scan
By default quarantine_hits=0 in /usr/local/maldetect/conf.maldet, so LMD only reports. Leave it that way until you have reviewed a few reports.
rkhunter and chkrootkit
# Debian/Ubuntu
sudo apt install rkhunter chkrootkit
# RHEL-family (EPEL)
sudo dnf install rkhunter
sudo rkhunter --check --sk --rwo # run all checks, no prompts, warnings only
sudo chkrootkit -q # quiet: only report suspicious findings
- rkhunter writes a full log to
/var/log/rkhunter.log. Its file-property checks compare against a baseline created withrkhunter --propupd. Only create that baseline on a system you trust: running it on a compromised server records the attacker’s files as “good”. On Debian, the packaged configuration disables--update; the data files are updated with the package. The last upstream release, 1.4.6, dates from 2018, so it mostly knows older rootkits. - chkrootkit 0.59 (January 2026) checks system binaries, log tampering and signs of kernel-module trojans. Use
-pto point it at trusted copies of the commands it runs, or-r /mnt/suspectto check a disk mounted from rescue media.
Both tools produce false positives; Debian’s rkhunter notes list, for example, hidden directories created by udev and Java. Investigate each warning rather than ignoring the whole report.
YARA rules
YARA matches files against patterns you describe. It is most useful when a security vendor or CERT publishes rules for a specific threat, such as the rule Trend Micro published for the Kinsing ActiveMQ exploit. A minimal example that flags a common web shell pattern:
cat > php-eval.yar <<'EOF'
rule php_eval_base64
{
strings:
$a = "eval(base64_decode(" nocase
condition:
$a
}
EOF
sudo apt install yara # Debian/Ubuntu; EPEL on RHEL-family
sudo yara -r -s php-eval.yar /var/www
-r scans recursively (and follows symlinks) and -s prints the matching strings. Only use rules from sources you trust.
Verify installed packages
Package managers know the checksum of every file they installed. Changed system binaries are a strong sign of tampering.
# Debian/Ubuntu
sudo dpkg --verify # all packages; "??5??????" means contents changed
sudo apt install debsums
sudo debsums -s # report only errors
sudo debsums -ca # changed files, including config files
# RHEL, AlmaLinux, Rocky, Fedora
sudo rpm -Va
In rpm -Va output, 5 means the file contents differ, S the size, M the permissions, and U/G the owner; a c marks a configuration file, which you would expect to change. A changed /usr/bin/ps, /usr/sbin/sshd or PAM module is serious; a changed /etc/ssh/sshd_config may just be your own edit.
The dpkg manual is clear that this is “only an integrity check and should not be considered as any kind of security verification”: an attacker with root can also change the stored checksums. To reinstall Debian packages whose files changed, the debsums manual gives this command (shown here with sudo added inside, so debsums can read every file):
sudo apt-get install --reinstall $(dpkg -S $(sudo debsums -c) | cut -d : -f 1 | sort -u)
What scanners miss
- No malware at all. An added SSH key, a new sudo user or a cron line that runs
curlare not “malware” to a scanner. Check them by hand: see Find backdoors. - Hidden files and processes. A rootkit loaded through
/etc/ld.so.preloadcan hide files from every normal program, including the scanner. Kernel-module rootkits hide even better. - Running code with no file. A process whose binary was deleted, or that runs only in memory, has nothing on disk to scan. Look for
(deleted)in the output ofsudo ls -l /proc/*/exe. - New or customised samples. Signatures cover known malware; obfuscated web shells are often missed.
- Database content. Injected JavaScript stored in a CMS database is not a file. See Website hacked.
Scan from rescue media
When root was compromised, boot the server from your provider’s rescue system or a live image, mount the disk read-only, and scan it from there:
lsblk # find the server's root partition
sudo mkdir -p /mnt/suspect
sudo mount -o ro /dev/sda1 /mnt/suspect
sudo clamscan --recursive --infected /mnt/suspect
sudo chkrootkit -r /mnt/suspect
When to call a professional
If scans flag system binaries, kernel modules or PAM files, or a scanner reports a rootkit, treat the server as fully compromised: plan a rebuild and get help from your host or an incident responder. Scanners are for triage, not proof of a clean system.
Sources
- Scanning, ClamAV documentation
- Packages, ClamAV documentation
- Linux Malware Detect, R-fx Networks
- Linux Malware Detect README, rfxn on GitHub
- rkhunter(8) manual page, Debian
- rkhunter.conf as packaged in Debian 13
- rkhunter README.Debian (false positives, baseline warnings)
- Rootkit Hunter project page
- chkrootkit project page
- Running YARA from the command line, YARA documentation
- debsums(1) manual page, Debian
- dpkg(1) manual page, Debian
- rpm(8) manual page, RPM project