How to check if your Linux server was hacked
How to check if a Linux server is hacked: logins, users, SSH keys, processes, network, cron, systemd, changed files and rootkits, with real commands.

Key takeaways
- Check logins, accounts and SSH keys first: they show how someone got in and how they plan to come back.
- Look for processes running from /tmp or /dev/shm, deleted binaries and unexpected outgoing connections.
- Persistence hides in cron, systemd timers, authorized_keys and /etc/ld.so.preload.
- A clean result does not prove a clean server; if root was compromised, rebuild rather than trust it.
To check if your Linux server was hacked, look in seven places: recent logins, user accounts and SSH keys, running processes, network connections, scheduled tasks and services, changed system files, and outgoing mail. Signs of compromise include logins from unknown IPs, a new UID 0 account, an SSH key you did not add, a process running from /tmp or /dev/shm, connections to unknown hosts and system binaries that fail package verification. The commands below work on Ubuntu and Debian and take about 20 minutes.
One warning before you start: if an attacker has root, they can make the tools lie. These checks catch common intrusions such as cryptominers, spam scripts and webshells, which are often noisy, but a clean result is not proof. If you find signs of root access, stop investigating on the live system and follow a rebuild plan such as our web server ransomware recovery playbook.
Signs that a server may be compromised
| Symptom | What it may mean | First check |
|---|---|---|
| CPU at 100% with no traffic | Cryptominer | ps aux --sort=-%cpu |
| Server IP on a spam blocklist | Spam script or hacked site | Mail queue, ss -tnp on port 25 |
| Unknown files in web folders | Webshell or backdoor | Malware scan, find by date |
| Provider abuse report about scanning or DDoS | Server used as a bot | ss -tupan, process list |
| Passwords changed, logins failing | Account takeover | last, auth log |
| Logs missing or truncated | Attacker covering tracks | journalctl --list-boots, log sizes |
| Firewall or SSH settings changed | Attacker keeping access | ufw status, sshd -T |
Before you start
- Take a snapshot at your VPS provider if you can. It keeps the evidence intact in case you need it later.
- Work as root over SSH from a trusted computer, and save output to a file outside the web root, for example
script /root/ir-$(date +%F).log. - Do not reboot and do not delete anything yet. Deleting a suspicious file before you know how it got there removes your best clue.
Step 1: who logged in, and from where?
last -F | head -30 # successful logins, with full dates
lastb -F | head -30 # failed logins (needs root)
who; w # who is logged in right now
journalctl -u ssh --since "-7 days" | grep -E "Accepted|Invalid user" | tail -50
grep -E "Accepted (password|publickey)" /var/log/auth.log* 2>/dev/null | tail -50
What to look for: Accepted password lines when you only use keys, logins from countries or providers you never use, logins at odd hours, and accounts that should never log in at all, such as www-data or a hosting account. Thousands of failed logins are normal background noise on any public server; a single success from a strange IP is not. (On some systems the SSH unit is called sshd rather than ssh.)
Step 2: accounts, sudo rights and SSH keys
awk -F: '$3 == 0 {print $1}' /etc/passwd # anything besides root is suspicious
awk -F: '$3 >= 1000 {print $1, $6, $7}' /etc/passwd
awk -F: '($2 == "") {print $1}' /etc/shadow # accounts with empty passwords
getent group sudo; ls -la /etc/sudoers.d/
find / -xdev -name authorized_keys -exec ls -la {} \; -exec cat {} \; 2>/dev/null
Look for a second UID 0 account, users you did not create, recently modified sudoers.d files and SSH keys with comments you do not recognise. Adding a key to authorized_keys is a standard persistence technique, catalogued by MITRE ATT&CK as T1098.004. Check root's keys and every user's, not just yours.
Step 3: processes
ps auxf --sort=-%cpu | head -30 # biggest consumers, as a tree
ls -l /proc/*/exe 2>/dev/null | grep -E "deleted|/tmp/|/dev/shm/|/var/tmp/"
ls -la /tmp /var/tmp /dev/shm
Red flags:
- a process running a binary that has been deleted from disk, a classic trick to hide;
- binaries running from
/tmp,/var/tmpor/dev/shm, which are writable by everyone; - names that imitate kernel threads (
[kworker]-like names) but show an executable path; - a web server or PHP user (
www-data, a hosting account) runningbash,perl,python,ncorcurlin a loop.
For any suspicious PID, ls -l /proc/<PID>/cwd, cat /proc/<PID>/cmdline | tr '\0' ' ' and ls -l /proc/<PID>/fd show where it runs from, how it was started and which files and sockets it holds.
Step 4: network connections
ss -tulpn # what is listening, and which process
ss -tupn state established # active connections with process names
Every listening port should map to a service you know: SSH, the web server, mail, your panel, databases on 127.0.0.1. Investigate a port you cannot explain, a listening process running as a web user, or outgoing connections from PHP or a hosting account to IRC ports, mining pools or many hosts on port 22 (your server scanning others).
Step 5: cron jobs, systemd units and other persistence
Attackers want to come back after a reboot or a cleanup. Check every scheduler, not just root's crontab:
for u in $(cut -d: -f1 /etc/passwd); do crontab -l -u "$u" 2>/dev/null | sed "s/^/$u: /"; done
ls -la /etc/cron.d /etc/cron.hourly /etc/cron.daily /var/spool/cron/crontabs 2>/dev/null
systemctl list-timers --all
find /etc/systemd /lib/systemd /usr/lib/systemd ~/.config/systemd -name "*.service" -mtime -14 2>/dev/null
cat /etc/ld.so.preload 2>/dev/null # should normally not exist
ls -la /etc/rc.local /etc/profile.d/ 2>/dev/null
Cron (T1053.003) and systemd services (T1543.002) are the usual places. Watch for lines that download and run something (curl ... | sh, wget -q -O-), base64 blobs and services with random names. /etc/ld.so.preload forces a library into every program; it is rarely used legitimately, and changing it is a known hijacking technique (T1574.006) that can hide files and processes from other tools.
Step 6: changed system files
On Debian and Ubuntu, the package manager knows the checksum of every file it installed:
dpkg --verify 2>/dev/null | grep -v " c /" # changed files, ignoring config files
sudo apt install debsums && sudo debsums -s # alternative: silent unless a file differs
find /usr/bin /usr/sbin /bin /sbin -newer /etc/hostname -type f 2>/dev/null | head
In dpkg's output, a 5 in the third column means the file's checksum no longer matches the package. That is expected for configuration files you edited, which is why the command filters lines marked c. It is not expected for /usr/bin/ls, /usr/sbin/sshd or ps. The -newer check depends on a reference file that was not touched since install; pick one that fits your server.
Then look at the websites, where most intrusions start:
find /var/www /home -name "*.php" -mtime -7 -type f 2>/dev/null | head -50
find /var/www /home -path "*uploads*" -name "*.php" -type f 2>/dev/null
grep -rlE "eval\(base64_decode|gzinflate\(base64_decode|assert\(\\\$_(POST|GET|REQUEST)" /var/www /home --include="*.php" 2>/dev/null | head
PHP files in upload folders and recently changed PHP files you did not deploy deserve a close look. For WordPress-specific cleanup, see how to clean a hacked WordPress site.
Step 7: logs, mail and rootkit scanners
journalctl --list-boots | tail # unexpected reboots?
ls -la /var/log/ | head -40 # empty or very small logs?
cat /root/.bash_history | tail -50; ls -la /root/.bash_history
postqueue -p 2>/dev/null | tail -5 # mail queue (Postfix)
A history file linked to /dev/null, logs that stop abruptly or a mail queue with thousands of messages are all signs worth following up.
Finally, run a rootkit scanner such as chkrootkit or rkhunter. Both are in the Ubuntu and Debian repositories. Treat their output as leads, not verdicts: both can report false positives on modern systems, and neither can see a well-built kernel rootkit.
What to do if you find something
- Do not tip off the attacker by deleting files one by one while they are still connected. Block network access at the provider first, except for your IP.
- Find the entry point from the timestamps you collected: the first unknown login, the first webshell, the first changed binary.
- Decide the scope. A webshell running as one hosting account, with no sign of root access, can usually be cleaned inside that account. Signs of root access (changed system binaries, new UID 0 user, root's keys or crontab modified,
ld.so.preload) mean you rebuild the server and restore data from a backup taken before the intrusion. - Rotate every credential that was on the server, and patch whatever let them in. Our SSH brute force guide covers a common way in for servers that still allow passwords.
Checking a ZoPanel server
ZoPanel automates part of this work. The malware scan checks the website files of every hosting account each Sunday at 03:00 and on demand for webshells, backdoors, obfuscated PHP, injected JavaScript and PHP files in upload folders, and lets you quarantine findings to a folder only root can read. Security Center, described in the security documentation, checks for extra UID 0 accounts, empty passwords, services exposed on public ports, SSH settings, pending updates and whether the hash-chained activity log is still intact. Alerts can notify you of failed panel logins, a login from a new IP and malware findings.
What it does not do: the malware scanner reads only files under each account's domains/ folder, up to 4 MB, with known script extensions, so a PHP payload hidden in a .jpg or outside domains/ is missed. It is not a rootkit detector and does not verify system binaries. Run the commands above as well, especially after any alert. Account isolation helps keep a hacked site from spreading; we explain the layers in shared hosting account isolation.
Conclusion
To check if a Linux server was hacked, work through logins, accounts and keys, processes, network, persistence, changed files and logs, and write down what you find with timestamps. Common intrusions usually leave traces in at least one of those places. If the traces point to root access, stop cleaning and rebuild.
If you want weekly malware scans, Security Center checks and alerts set up for you, try the ZoPanel Free plan: 3 hosting accounts, 10 websites, 10 databases, no time limit. The security features page lists the layers in more detail.
Frequently asked questions
How do I know if someone accessed my Linux server?
Check last -F and the SSH log (journalctl -u ssh or /var/log/auth.log) for successful logins, then compare the IPs and times with your own. Also check authorized_keys for every user, because an attacker who added a key can log in without a password.
Can a hacked server look completely normal?
Yes. A careful attacker with root can hide processes, files and connections from standard tools, for example with a rootkit or /etc/ld.so.preload. That is why a clean scan is not proof, and why servers with signs of root compromise should be rebuilt.
Which tools can scan a Linux server for malware?
For server-level checks, chkrootkit and rkhunter look for known rootkits and suspicious changes, and dpkg --verify or debsums compare installed files with package checksums. For website files, use a malware scanner that understands webshells and obfuscated PHP, such as the one built into your control panel.
What should I do first if my VPS is hacked?
Take a snapshot at your provider, restrict network access to your own IP, and do not reboot or delete files yet. Then find the entry point, decide whether root was compromised, and either clean the affected account or rebuild the server from clean backups.
Why is my server's CPU at 100% with no visitors?
A common cause on compromised servers is a cryptominer, often started from /tmp or /dev/shm and restarted by a cron job. Check ps aux --sort=-%cpu, look at where the top process runs from with ls -l /proc/<PID>/exe, and check every user's crontab.
Install ZoPanel free
One command on a fresh Ubuntu or Debian server. Up and running in minutes.
Install in 5 minutesKeep reading

Hosting account resource limits: CPU, RAM and I/O done right
What hosting account resource limits for CPU, RAM, I/O, processes and inodes do, how Linux enforces them, and how to size plans so one site can't sink a server.

Hosting panel security incidents 2024–2026: what went wrong and what to learn
A sourced timeline of hosting control panel vulnerabilities 2024–2026 (cPanel, CyberPanel, CWP, Plesk, HestiaCP), the patterns and a defense checklist.

Hosting server security checklist: 40 checks
A hosting server security checklist with 40 checks for a VPS that hosts websites: updates, SSH, firewall, panel, account isolation, web layer and backups.