Back to blog
security

Your server is at 100% CPU: finding and removing a crypto miner

A step-by-step removal runbook for cryptojacking on Linux: identify the miner (even when it hides), kill it properly, dig out the persistence that will bring it back, close the entry hole, and detect the next one in minutes instead of weeks.

Sandeep Sidhu

· Founder, AlertKick

4 min read
Your server is at 100% CPU: finding and removing a crypto miner

Removing a crypto miner from a Linux server takes four steps: identify the process with top -o %CPU and confirm it with ss -tupan (miners must talk to a pool, typically on ports 3333, 5555, 7777, or 14444), kill the process group after saving evidence, dig out the persistence in cron, systemd, and authorized_keys, then close the entry hole - usually a weak SSH password or an unpatched web app. All four stages, with commands, are below. Skipping step three is why the miner is back by morning.

If you’re seeing load through the roof, fans screaming or the cloud bill climbing, and a process you’ve never heard of eating every core: odds are your server is mining cryptocurrency for someone else. Cryptojacking is the most common fate of a compromised Linux box because it’s the easiest way to turn stolen CPU into money - attackers scan the whole internet for weak SSH passwords and unpatched web apps, and they don’t care whose server it is.

1. Find the miner

top -o %CPU            # the obvious check - what's eating the cores?
ps auxwwf | less       # full process tree: look at parents, not just the hog

Miners rarely call themselves xmrig anymore. Watch for:

  • Names mimicking system processes: kworkerd, kdevtmpfsi, systemd-networks - close-but-wrong spellings of real kernel threads.
  • Processes running from /tmp, /dev/shm, /var/tmp, or a home directory.
  • A deleted binary still running: ls -la /proc/<pid>/exe showing (deleted) is a miner classic - the file removed itself after launch to dodge disk scans.
  • CPU capping tricks: some miners hold themselves at 50-70% to stay under alert thresholds, or pause when someone’s logged in. If load history looks sawtoothed around your login times, be suspicious anyway.

Confirm with the network. Miners must talk to a pool.

ss -tupan | grep -v 127.0.0.1

Established outbound connections to unfamiliar IPs - frequently on ports 3333, 4444, 5555, 7777, 14444, or wrapped in TLS on 443 - are your confirmation. Check the destination IP against a threat feed; mining pools are well-catalogued.

2. Kill it properly

Grab evidence first - you’ll want it for step 4:

ls -la /proc/<pid>/exe /proc/<pid>/cwd
cat /proc/<pid>/cmdline | tr '\0' ' '; echo
ss -tupan | grep <pid>

Then stop it without giving it a chance to respawn cleanly:

kill -STOP <pid>        # freeze first - some miners watch for SIGTERM and respawn
kill -KILL <pid>

If CPU spikes again within minutes, you didn’t kill the miner - you killed its child. A watchdog is running. That’s expected; it’s what stage 3 is for.

3. Dig out the persistence

This is the stage that separates “removed it” from “removed it until Tuesday”. Check every launcher on the system:

crontab -l; ls -la /etc/cron.* /var/spool/cron 2>/dev/null    # cron - the #1 spot
systemctl list-unit-files --state=enabled | grep -v static     # rogue services
cat /etc/rc.local 2>/dev/null
grep -r "curl\|wget\|base64" /etc/profile.d/ ~/.bashrc ~/.profile 2>/dev/null
cat ~/.ssh/authorized_keys /root/.ssh/authorized_keys 2>/dev/null  # added keys
ls -la /etc/ld.so.preload 2>/dev/null   # should usually not exist - preload rootkit

Miner droppers routinely install a cron entry that re-downloads the payload every few minutes, a systemd unit with an innocuous name, an SSH key for re-entry, and sometimes an ld.so.preload rootkit that hides the process from ps and top entirely. If ld.so.preload exists and you didn’t put it there, treat the box as fully rooted.

Also check for resource-limit tampering (/etc/security/limits.conf) and a modified /etc/hosts blocking security-vendor domains. Both are common dropper behaviour.

4. Close the door they used

The miner is a symptom. Entry was almost certainly one of:

  • SSH brute force or a stolen key - check lastb | head -30 for the brute-force trail and last -a for the successful entry. Fix: key-only auth, no root login, and alerts on every SSH login.
  • An unpatched web app or framework RCE - check your web server’s access log around the miner’s file timestamps for odd POSTs.
  • An exposed service that was never meant to be public: Redis with no auth, Docker’s API port, Jenkins. ss -tlpn and an honest look at your firewall.

Rotate every credential the box held. If you found a rootkit, a modified system binary, or you can’t fully explain the entry, rebuild the server rather than trust the cleanup. Restore data, not binaries.

5. Shrink the “weeks” to “minutes”

Dwell time costs more than removal. Miners run undetected for weeks, inflating cloud bills, degrading service, and giving attackers persistent access.

AlertKick’s eBPF agent watches every signal in this runbook continuously: processes launched from /tmp and /dev/shm, execution of deleted binaries, outbound connections matched against live threat-intel feeds (mining pools included), new cron entries and authorized_keys changes via file integrity monitoring, and the SSH login that started it all. Each event maps to MITRE ATT&CK and gets explained in plain English by AI triage.

A miner costs more in stolen CPU than a year of monitoring. Start free, install the agent in a minute, and catch the next dropper at stage zero - the login - instead of on the bill.

Frequently asked questions

How do you find a crypto miner on a Linux server?
Start with top -o %CPU and ps auxwwf, looking at parent processes rather than just the CPU hog. Miners rarely call themselves xmrig; watch for names mimicking kernel threads like kworkerd or kdevtmpfsi, processes running from /tmp, /dev/shm, /var/tmp, or a home directory, and a /proc/<pid>/exe link showing (deleted). Confirm with ss -tupan: miners must talk to a pool, typically on ports 3333, 4444, 5555, 7777, or 14444, or wrapped in TLS on 443.
How do you remove a crypto miner from a Linux server?
Four steps: identify the process and confirm its pool connection, save evidence from /proc/<pid>/exe, its cmdline, and ss output, then freeze it with kill -STOP before kill -KILL so it cannot respawn cleanly, dig out persistence in cron, systemd units, rc.local, shell profiles, authorized_keys, and ld.so.preload, and finally close the entry hole. Skipping the persistence step is why the miner is back by morning.
Why does the crypto miner keep coming back after you kill it?
Because you killed a child and a watchdog is still running, or a cron entry is re-downloading the payload every few minutes. Miner droppers routinely install a cron job, a systemd unit with an innocuous name, an SSH key for re-entry, and sometimes an ld.so.preload rootkit that hides the process from ps and top entirely. Check every launcher on the system, not just the process.
How did the crypto miner get onto the server?
Almost always one of three ways: SSH brute force or a stolen key (check lastb for the brute-force trail and last -a for the successful entry), an unpatched web app or framework RCE (check the web server access log around the miner's file timestamps for odd POSTs), or an exposed service that was never meant to be public such as Redis with no auth, Docker's API port, or Jenkins.
Should you rebuild a server after removing a crypto miner?
If you found a rootkit, a modified system binary, or an ld.so.preload file you did not create, or you cannot fully explain the entry, rebuild rather than trust the cleanup. Restore data, not binaries, and rotate every credential the box held.
How do you detect cryptojacking before it shows up on the bill?
The expensive part is dwell time - the average miner runs undetected for weeks. Continuous kernel-level monitoring catches the signals as they happen: processes launched from /tmp and /dev/shm, execution of deleted binaries, outbound connections matched against threat-intel feeds that include mining pools, new cron entries and authorized_keys changes via file integrity monitoring, and the SSH login that started it.
security cryptojacking linux incident response