The CPU sits at 100% with no traffic. Your provider forwards an abuse complaint. The website redirects to a pharmacy shop. These are typical signs of a compromised server. This guide lists the warning signs, shows what to check, and covers what to do if you find something. The short version: if root was compromised, rebuild.

Common warning signs

  • CPU constantly high, with no matching traffic: crypto-miners are the most common payload on hacked Linux servers.
  • Abuse reports from your provider or others: your server is sending spam, scanning or attacking other hosts.
  • Unknown processes, often with random or deceptive names (kworkerds, [kthreadd] copies, .x, dbused).
  • New users or SSH keys you didn’t add.
  • Cron jobs or systemd services you don’t recognise.
  • Website changes: injected JavaScript, spam pages, redirects, new PHP files in upload directories.
  • Unusual outbound connections: to mining pools, IRC, or many IPs on port 22 or 25.
  • Logs cleared or missing, history empty, and odd timestamps.
  • Disk filling with unknown files in /tmp, /dev/shm or /var/tmp.

A check, step by step

Treat the output with suspicion. A sophisticated attacker with root can hide from local tools. Use the VNC console if SSH behaves strangely.

1. Who is logged in and who logged in recently?

w
last -a | head -30
lastb | head -20                      # failed logins
journalctl -u ssh --since "7 days ago" | grep -E 'Accepted|session opened' | tail -30

Accepted logins from IPs you don’t recognise are a major red flag.

2. Processes

ps auxf --sort=-%cpu | head -30
sudo ls -l /proc/*/exe 2>/dev/null | grep -E 'deleted|/tmp|/dev/shm'

Look for processes running from temp directories, binaries that were deleted after starting, and processes owned by www-data that aren’t PHP or the web server. See /proc filesystem.

3. Network connections

sudo ss -tunap | grep -v '127.0.0.1'
sudo ss -tunap state established

Unexpected outbound connections, for example to ports 3333, 4444, 5555 or 14444 (mining pools), many connections to port 25 (spam), or listeners on odd ports. See ss.

4. Users and keys

awk -F: '$3 == 0 {print}' /etc/passwd            # UID 0 accounts other than root?
awk -F: '$3 >= 1000 {print $1, $6, $7}' /etc/passwd
getent group sudo wheel
for h in /root /home/*; do echo "== $h"; cat "$h/.ssh/authorized_keys" 2>/dev/null; done

Any unknown key in authorized_keys means the attacker can come back.

5. Persistence: cron, systemd, startup files

sudo ls -la /etc/cron.* /var/spool/cron/crontabs/ /etc/cron.d/
for u in $(cut -d: -f1 /etc/passwd); do sudo crontab -l -u "$u" 2>/dev/null | sed "s/^/$u: /"; done
systemctl list-unit-files --state=enabled | grep -v -E '^(systemd|dbus|getty)'
ls -lt /etc/systemd/system/ | head
cat /etc/rc.local 2>/dev/null; cat /etc/ld.so.preload 2>/dev/null

/etc/ld.so.preload should normally not exist. If it lists a library, that’s a classic rootkit technique.

6. Modified system files

sudo dpkg --verify 2>/dev/null | grep -v ' c '       # Debian/Ubuntu: changed package files (excluding configs)
sudo rpm -Va 2>/dev/null | grep -v ' c '              # RHEL family
sudo rkhunter --check --sk --rwo

Changed binaries like ps, ss, netstat, ls or sshd strongly suggest a rootkit. See rkhunter. If you run AIDE, check its report. See AIDE.

7. Website files

find /var/www -name '*.php' -mtime -7 -printf '%TY-%Tm-%Td %TH:%TM %p\n' | sort | tail -30
find /var/www -path '*/uploads/*' -name '*.php'
grep -rlE 'eval\(base64_decode|gzinflate\(|str_rot13\(|assert\(\$_' /var/www --include=*.php | head
wp core verify-checksums --path=/var/www/example.com       # WordPress

PHP files in upload directories, and obfuscated eval(base64_decode(…)) code, are classic web shells. See ClamAV.

If you find a compromise

Web application only (a web shell running as www-data, no sign of root access):

  1. Take the site offline or into maintenance mode.
  2. Find the entry point (an outdated plugin, weak admin password, file upload flaw).
  3. Restore clean code from a trusted source or backup, update everything, and remove the malicious files and admin users.
  4. Rotate all passwords, keys and salts the app used (database, API keys, wp-config salts).
  5. Watch closely for reinfection.

Root-level compromise (unknown root processes, modified binaries, rootkits, unknown keys for root):

  1. Isolate the server. Firewall all but your own IP, or ask your provider for help.
  2. Preserve evidence if needed: copy logs and suspicious files off the server.
  3. Rebuild: reinstall the OS (VPS.DO’s control panel offers OS reinstall), harden it, and restore data (not system files or binaries) from backups made before the compromise.
  4. Rotate everything: SSH keys, passwords, database credentials, API tokens, and any keys stored on the server.
  5. Fix the entry point before going live again.

Don’t try to clean a root compromise in place. You can’t trust anything on the system any more.

Prevention

  • Key-only SSH, Fail2Ban, and a firewall that exposes only what’s needed. See key-only SSH.
  • Automatic security updates, plus updated CMS plugins. See unattended upgrades.
  • Never expose databases, Redis or the Docker API publicly. See nmap.
  • Off-server, versioned backups.
  • Monitoring: CPU alerts, login notifications (SSH login alerts), file integrity checks.

See the full Linux server hardening checklist.

  • Security
  • Linux
  • Incident Response