Scanning Linux Servers for Malware: Tools and Scheduling

Scan Linux servers with three tools together: ClamAV for signatures, Maldet for malicious PHP, and rkhunter for rootkits. Schedule them at night with low priority, and when an infection appears, isolate the server and rebuild instead of cleaning.
Scanning a Linux server for malware takes three tools working together rather than one: ClamAV for signature-based file scanning, Maldet (Linux Malware Detect) for catching malicious PHP scripts in upload directories, and rkhunter for spotting rootkits and tampered system binaries. Run them at night between 2 and 5 AM with low priority through nice and ionice, and send the reports to your inbox or alerting channel. When a real infection shows up, deleting the file is not enough. Isolate the server, work out how the attacker got in, then rebuild a clean server from scratch and move the data over after scanning it, because an attacker who left one visible file has usually left two hidden ways back in.
This guide covers the common signs of infection, what each tool catches and misses, a practical schedule that stays out of the way during peak hours, and a step-by-step response plan.
Signs of Infection You Can See Before Any Tool
Most infections on Linux servers today are not viruses in the old sense. They are scripts that exploit a web application bug or a weak password and then put your resources to work. These are the most frequent signs, roughly in order of how often they appear:
- CPU pinned above 90% by a process with an odd name such as
kdevtmpfsi,xmrig, or a random 8-character string, which usually means cryptocurrency mining. - PHP files inside upload directories like
uploadsorstorage/app/public, containing calls such aseval(base64_decode(orgzinflatewrapped around a long encoded blob. - Cron jobs you never added, pulling a script from an external address every minute through
curlorwget. - New SSH keys in
~/.ssh/authorized_keysfor root or the application user. - Unexplained outbound connections on ports like 3333, 4444, or 6667, visible in
ss -tunp. - Thousands of outgoing emails from a PHP script, which you often notice when your legitimate mail stops reaching customers' inboxes.
A quick manual check takes under 5 minutes and gives you a first picture:
top -b -n1 | head -20
ss -tunp | grep ESTAB
crontab -l; ls -la /etc/cron.d /var/spool/cron
find /var/www -name "*.php" -path "*uploads*" -mtime -7
grep -rl "eval(base64_decode" /var/www --include=*.php | head
ClamAV, Maldet, and rkhunter: What Each One Catches
A common mistake is installing ClamAV alone and calling the server protected. ClamAV was designed mainly for attachments and executables, and its signature database is relatively weak against hand-modified web scripts. That is why we combine all three:
| Tool | Good at catching | Misses | Rough scan time for 50GB |
|---|---|---|---|
| ClamAV | Known executables, attachments, well-known miners | New obfuscated PHP scripts | 40 to 90 minutes |
| Maldet | Webshells, PHP injection, files in uploads | Kernel-level rootkits | 20 to 45 minutes |
| rkhunter | Rootkits, modified binaries like ls and ps, suspicious ports | Web application content | 2 to 5 minutes |
Installing ClamAV and Updating Signatures
apt install clamav clamav-daemon -y
systemctl stop clamav-freshclam
freshclam
systemctl enable --now clamav-freshclam
The freshclam service refreshes signatures automatically up to 24 times a day, and the daemon holds roughly 1GB of memory once the database is loaded. If your server has only 2GB, skip the daemon and use plain clamscan during the scheduled scan.
Maldet with the ClamAV Engine
Maldet reads ClamAV signatures when they are available, which makes scans faster and more accurate. After installing it, edit /usr/local/maldetect/conf.maldet:
email_alert="1"
email_addr="ops@example.sa"
scan_clamscan="1"
quarantine_hits="1"
quarantine_clean="0"
scan_ignore_root="0"
quarantine_hits moves an infected file into quarantine instead of deleting it, which matters because you will need that file later to understand how the attacker got in. Leave quarantine_clean off, since automatic "cleaning" can break a legitimate file that had one line injected into it.
rkhunter and the Baseline
apt install rkhunter -y
rkhunter --update
rkhunter --propupd
rkhunter --check --sk --rwo
--propupd records the fingerprints of current system files as the baseline. Run it only on a server you know is clean and after every package update, otherwise you will record a tampered binary as the original.
Scheduling Scans at Night With Minimal Performance Impact
A full disk scan reads every byte, and on a 200GB disk it can push database latency from 2ms to 30ms while it runs if you leave it at normal priority. The fix has three layers: smart timing, low priority, and incremental scanning.
# /etc/cron.d/malware-scan
# Daily incremental scan of web files changed in the last 2 days
30 2 * * * root nice -n 19 ionice -c3 /usr/local/sbin/maldet -r /var/www 2 >/dev/null 2>&1
# Daily rkhunter check
15 3 * * * root /usr/bin/rkhunter --cronjob --report-warnings-only | mail -s "rkhunter $(hostname)" ops@example.sa
# Full weekly ClamAV scan on Friday night
0 3 * * 5 root nice -n 19 ionice -c3 clamscan -ri --exclude-dir="^/(proc|sys|dev)" / > /var/log/clamscan-weekly.log
ionice -c3 lets the scan use the disk only when nothing else needs it, and nice -n 19 gives it the lowest CPU priority. Watch the server's own clock too. If it runs on UTC, 2:30 there means 5:30 AM in Riyadh, so set the timezone first with timedatectl set-timezone Asia/Riyadh to keep the scan away from the start of the working day.
On managed cloud hosting server-level scanning is part of the management, while on a cloud server you have full root access and own this schedule yourself.
What to Do When You Find an Infection
The first alert will tempt you to delete the file and move on, and that is the biggest mistake you can make. Follow this order instead:
- Do not power off immediately, since memory holds processes and connections you need for the investigation. Save the output of
ps auxf,ss -tunp, andlast -50to a file off the server. - Isolate the network with a firewall that allows only SSH from your address, which stops mining, spam, or contact with the command server.
- Take a snapshot or copy of the disk as it is, because that is your evidence for finding the entry point.
- Find the entry point by reviewing web logs around the infected file's creation time (
statgives you the timestamp), searching for POST requests to that path, and checking/var/log/auth.logfor successful SSH logins. - Rotate every secret, including database passwords, API keys, and SSH keys, because the
.envfile was most likely read. - Build a new server, move the data across after scanning it, and close the hole before going live again.
Why Rebuilding Is Safer Than Cleaning
Cleaning assumes you know everything the attacker did, and that assumption rarely holds. Published incident analyses show that a large share of "cleaned" servers get reinfected within 30 days, because attackers typically leave more than one route back: a new system user, an SSH key, a systemd service named like a real one, and a second webshell with an innocent name such as wp-cache.php. With a kernel-level rootkit, even ls and ps will lie to you.
Rebuilding on a cloud server has become cheap in time. A new server activates within seconds, installing packages with a script or Ansible takes 10 to 30 minutes, and you then deploy code from Git rather than from the infected machine, move the database after reviewing user and privilege tables, and copy uploads only after scanning them with Maldet and removing any PHP files. The Starter plan costs 3,450 SAR a year including VAT, roughly 9.5 SAR a day, which is far less than a lost workday spent hunting a backdoor you cannot locate.
Backups are your last line of defense, with one catch: a backup taken after the infection is infected too. That is why longer retention matters, and the daily backup add-on with 30-day retention at 2,000 SAR a year gives you a restore point from before the infection even if you only discover it two weeks later.
A Practical Plan for This Week
- Install ClamAV, Maldet, and rkhunter, then run
rkhunter --propupdonce you are confident the server is clean. - Set the timezone and add the cron file above with your own email address.
- Block PHP execution inside upload directories in Nginx, which on its own stops most webshells:
location ~* /uploads/.*\.php$ { deny all; } - Prepare a script that builds the server from scratch and test it once on a throwaway server, so rebuilding after an infection is an easy decision rather than a project.
- Check pricing and plans to add the 30-day backup retention if your data is critical.
Regular scanning does not prevent a breach, but it shrinks the gap between infection and detection from weeks to hours, and that gap is what decides how much damage gets done.


