# 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.

Source: https://zopanel.net/blog/check-if-linux-server-hacked  
Updated: 2026-10-21

## 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](/blog/web-server-ransomware-recovery).

## 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?

```bash
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

```bash
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](https://attack.mitre.org/techniques/T1098/004/). Check root's keys and every user's, not just yours.

## Step 3: processes

```bash
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/tmp` or `/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) running `bash`, `perl`, `python`, `nc` or `curl` in 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

```bash
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:

```bash
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](https://attack.mitre.org/techniques/T1053/003/)) and systemd services ([T1543.002](https://attack.mitre.org/techniques/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](https://attack.mitre.org/techniques/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:

```bash
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](https://manpages.debian.org/bookworm/dpkg/dpkg.1.en.html), 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:

```bash
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](/blog/wordpress-malware-cleanup).

## Step 7: logs, mail and rootkit scanners

```bash
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](https://www.chkrootkit.org/) or [rkhunter](https://sourceforge.net/projects/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

1. **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.
2. **Find the entry point** from the timestamps you collected: the first unknown login, the first webshell, the first changed binary.
3. **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.
4. **Rotate every credential** that was on the server, and patch whatever let them in. Our [SSH brute force guide](/blog/ssh-brute-force-protection) covers a common way in for servers that still allow passwords.

## Checking a ZoPanel server

ZoPanel automates part of this work. The [malware scan](/docs/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](/docs/security), 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](/blog/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](/pricing): 3 hosting accounts, 10 websites, 10 databases, no time limit. The [security features page](/features/security) 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.
