SSD Nodes Learn Hosting plans →
Guides Matt ConnorBy Matt Connor

kswapd0 Using 100% CPU: Memory or a Miner?

A busy kswapd0 is the kernel reclaiming memory or another program using its name. Check its parent PID, owner and exe link, then fix memory or investigate.

Is kswapd0 at 100% CPU memory pressure or malware?

When kswapd0 sits at 100% CPU at the top of top, it is one of two things. It may be the kernel's own page-reclaim thread, working hard because the server is short of memory. Or it may be an ordinary program that someone named kswapd0 so it would blend in. The Outlaw botnet's crypto miner hides this way. The two need opposite fixes, so the first job is to tell them apart. That takes a few minutes and four checks: the parent PID, the owning user, the command line and the /proc/<pid>/exe link.

The commands below were written for Ubuntu 24.04 and run as a normal user with sudo. The output on your own server is what counts. This guide therefore describes what to look for and why, and does not print sample output.

What the real kswapd0 does

The kernel keeps a reserve of free memory. Sometimes an allocation pushes free memory below a level the kernel documentation calls the low watermark. That allocation then wakes the kswapd daemon. kswapd scans memory pages in the background and frees the ones that can be reused. The kernel calls this process reclaim. If free memory keeps falling and reaches the min watermark, the program that asked for memory must reclaim pages itself, and it stalls while it does. This is called direct reclaim.

There is one kswapd thread per memory node. The kernel source names each one kswapd%d, with the node number in place of %d. A VPS with a single node therefore has exactly one, kswapd0. A machine with several NUMA (non-uniform memory access) nodes also has kswapd1 and up. ls /sys/devices/system/node/ shows how many nodes your server has.

A busy kswapd0 is a symptom. It means reclaim cannot keep up, because the programs on the server want more memory than the server has. The kernel keeps scanning for pages to free, and that scanning is the CPU you see.

Swap changes what reclaim can do. Memory that programs allocate for their own data is called anonymous memory. It can only leave RAM by being written to swap. With no swap, kswapd can only take file-backed pages, such as the page cache and the code of running programs. Those pages are often needed again soon, so they are read back from disk and the cycle repeats.

Why a crypto miner calls itself kswapd0

Kaspersky's Securelist team published a write-up of the Outlaw botnet, also called Dota, on 29 April 2025. Outlaw is a Perl-based crypto-mining botnet that, in Securelist's words, "typically takes advantage of weak or default SSH credentials for its operations." In the incident they describe, the miner was a file named kswapd0 at a/kswapd0 inside a hidden .configrc5 directory in the user's home. It was a UPX-packed ELF binary: a modified build of XMRig 6.19.0, which mines the Monero cryptocurrency on the CPU.

Securelist does not explain the choice of name. The likely reason is simple. An admin who sees kswapd0 using CPU on a slow server may assume it is the kernel and leave it alone. The same campaign's Perl backdoor disguises itself as an rsync process, so a familiar name proves nothing by itself.

The reverse is also true: a process named kswapd0 that is not the kernel is not automatically a miner. Linux lets any program choose the name it shows. The man page for PR_SET_NAME describes it as a call to "Set the name of the calling thread", with a limit of 16 bytes including the terminating null byte, which kswapd0 easily fits. The name tells you a program is imitating the kernel thread. Only the checks below tell you what the program is and who started it.

Securelist also shows what the attackers do to SSH. Their script runs cd ~ && rm -rf .ssh && mkdir .ssh. It then adds the attackers' public key to authorized_keys and runs chmod -R go= ~/.ssh. The key belongs to a user called mdrfckr, a string Securelist finds in Dota campaigns. Because the old .ssh directory is deleted first, the account's own keys are gone. If your key login to that account suddenly stopped working, this is a likely reason.

Step 1: list every process named kswapd0

pgrep -a kswapd0
ps -eo pid,ppid,user,stat,%cpu,etime,args --sort=-%cpu | head -n 15

pgrep -a prints the PID of each process whose name matches, with its command line if it has one. On a single-node VPS the kernel runs exactly one kswapd0. If you see two, at least one of them is not the kernel.

The ps line lists the busiest processes with their parent PID (PPID), owner, CPU share and running time. Use it to find the PID of the kswapd0 that is actually using the CPU, because that is the one to inspect.

Put the PID you found into a variable, then run the checks:

PID=1234   # replace with the kswapd0 PID from step 1
grep -E '^(Name|PPid|Uid|Kthread):' /proc/$PID/status
tr '\0' ' ' < /proc/$PID/cmdline; echo
sudo readlink /proc/$PID/exe
ps -o pid,comm -p 2

Here is what each one tells you.

The parent PID. PPid in /proc/<pid>/status is the PID of the process that created this one. The kernel creates its threads through a helper thread called kthreadd. During boot the kernel starts init first so that init gets PID 1, then it starts kthreadd. That order is why kthreadd is normally PID 2. The last command prints the name of PID 2, so you can confirm it on your own server. The real kswapd0 has kthreadd as its parent. Anything started from user space, such as a shell or a cron job, has some other parent. A process keeps its PPID when it replaces itself with another program, so renaming a binary does not give it a kernel parent.

The owner. The Uid line lists four user IDs: real, effective, saved and filesystem. Kernel threads run as root. A kswapd0 owned by a normal account such as ubuntu or www-data is a program that account started. Root ownership does not clear a process, though, because an attacker with root can run anything as root.

The command line. /proc/<pid>/cmdline holds the arguments the program was started with. They are separated by null bytes, which is why tr turns the nulls into spaces. A kernel thread was never started from a command, so expect an empty line here. This is also why ps shows kernel threads with their name in square brackets: the ps manual says that when a process's arguments are unavailable, ps reports the executable name in brackets. A user-space program can end up shown in brackets too, so brackets alone prove nothing.

The exe link. /proc/<pid>/exe is a symbolic link to the file the process is running. A kernel thread has no executable on disk, so expect no target at all. Reading this link for another user's process needs root, which is why the line uses sudo. If the link resolves to a real file, such as /home/ubuntu/.configrc5/a/kswapd0, someone put that binary there. The manual gives one more signal: if the file was deleted after the process started, the link target ends in (deleted). A process named after a kernel thread that runs a deleted binary is hiding.

The Kthread flag. Recent kernels add a Kthread: line to /proc/<pid>/status. The kernel documentation defines it as the kernel thread flag, where 1 means yes and 0 means no. If your kernel does not print this line, grep simply skips it, and the other checks still answer the question.

Put together, a genuine kswapd0 has kthreadd as its parent, root as its owner, an empty command line and no exe target. If it fails any one of those, it is not the kernel thread, and the miner path further down helps you find out what it is. If it passes all of them, the server is short of memory, and the next sections find out why.

The memory path: confirm the server is short of RAM

free -h
vmstat 1 5

In the free output, read the available column, not the free column. available is the kernel's estimate of how much memory new applications can get without swapping. It counts page cache and reclaimable slab memory that the kernel can give back. A low free with a healthy available is normal, because Linux fills idle RAM with cache. A small available is the real shortage.

vmstat 1 5 prints five reports, one second apart. Ignore the first line, because it shows averages since the last reboot. In the later lines, si is memory swapped in from disk per second and so is memory swapped out to disk per second. Steady non-zero values in both while kswapd0 is busy mean the server keeps pushing pages out and pulling them back in. If swpd reads 0, no swap is in use. Run swapon --show to see whether any swap exists at all. It prints nothing when there is none.

Find the process that is actually eating RAM

kswapd0 does the work of reclaim. Some other process creates the demand.

ps -eo pid,user,rss,%mem,args --sort=-rss | head -n 10

rss is resident memory, the part of the process held in RAM, in kilobytes. The top lines are your candidates. Look at whether one process is far ahead of the rest, or whether the total is simply more than the plan has. If the server runs containers, docker stats --no-stream shows memory use per container, and setting memory limits in Docker Compose covers how to cap them.

Check the kernel log for OOM-killer lines

When reclaim cannot free enough memory, the kernel calls the OOM (out of memory) killer. The kernel documentation says it "selects a task to sacrifice for the sake of the overall system health." Each kill is written to the kernel log, so search for it:

sudo journalctl -k -g "out of memory|killed process"
sudo journalctl -k -b -1 -g "out of memory|killed process"
sudo dmesg -T | grep -iE "out of memory|killed process"

-k shows only kernel messages, from the current boot. -b -1 selects the boot before that, which matters when the server froze and was restarted. If that second command finds no previous boot, the journal on this server is not kept across reboots. -g filters by a regular expression, and it ignores case when the pattern is all lowercase. dmesg reads the kernel ring buffer directly. Its -T option prints readable times, but the manual warns those times can be inaccurate. If dmesg without sudo says permission denied, the dmesg_restrict kernel setting is the usual cause.

In the kernel source, the kill message has this format:

%s: Killed process %d (%s) total-vm:%lukB, anon-rss:%lukB, file-rss:%lukB, shmem-rss:%lukB, UID:%u

The first field is Out of memory for a server-wide shortage, or Memory cgroup out of memory when a service hit its own memory cap. The name in parentheses is the process that was killed. anon-rss is roughly how much of its own memory it held in RAM. Your kernel may print extra fields after UID. For more on reading these messages, see how to read the kernel log on a VPS.

How to fix kswapd0 high CPU from memory pressure

The fix is to make the memory the workload needs fit the memory the server has. There are four ways to do that.

Add RAM

If available stays low under normal load, the plan is too small for what runs on it. Resizing to a larger plan is the direct fix, and it is the only one that adds memory.

Add a sensible swap file

Swap gives the kernel somewhere to put anonymous memory that is rarely touched. Reclaim then no longer has only the page cache to work with. Check your root filesystem type first with df -T /. On ext4:

swapon --show
sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
swapon --show

The final swapon --show should list /swapfile. On XFS, create the file with dd instead of fallocate. The swapon manual warns that a file made by fallocate can count as a file with holes on some filesystems, and swapon rejects those. On Btrfs, use btrfs filesystem mkswapfile. Swap absorbs spikes. It does not replace RAM, so if si and so stay high after you add it, the server still needs more memory. Whether your VPS needs swap, and how much covers sizing and the cases where swap is the wrong fix.

Cap the service that grows

If one service grows until the box runs out, cap it with systemd. It then slows down or dies alone, and the rest of the server keeps running:

sudo systemctl set-property myapp.service MemoryHigh=1G MemoryMax=1200M
systemctl show myapp.service -p MemoryHigh -p MemoryMax

MemoryHigh is a throttle: above it, the service's processes are heavily slowed down. MemoryMax is the hard limit. If usage cannot be held under it, the OOM killer runs inside that service only, and the log line starts with Memory cgroup out of memory. The systemd manual recommends MemoryHigh as the main control and MemoryMax as the last line of defense. set-property applies the change at once and stores it for future boots, unless you pass --runtime. Replace myapp.service and the sizes with your own. Limiting a process's memory and CPU with systemd covers drop-in files and how to test a cap.

Make the workload smaller

Many programs size themselves from settings you control, such as worker counts and cache sizes. If the top process in the ps list has a setting like that, lowering it is often the cheapest fix of all.

Why dropping caches and tuning swappiness do not fix it

Two pieces of advice show up in almost every forum thread about kswapd0. Neither one changes how much memory the workload needs.

Dropping caches. Writing to /proc/sys/vm/drop_caches throws away clean page cache, reclaimable slab objects, or both. The kernel documentation says it frees no dirty objects. It also says these objects "are automatically reclaimed by the kernel when memory is needed elsewhere on the system." So the memory it frees is memory kswapd could already take. The documentation warns that it can cause performance problems, and that "use outside of a testing or debugging environment is not recommended." After a drop, free looks better for a moment. Then the cache refills and kswapd0 returns.

Tuning swappiness. vm.swappiness tells the kernel how expensive swap is compared with dropping file pages. The upstream kernel documentation gives a range of 0 to 200 and a default of 60. Check your own value with cat /proc/sys/vm/swappiness instead of assuming it. Changing it only shifts which pages the kernel prefers to reclaim. It adds no memory, and with no swap at all it has nothing to shift towards.

The sudo echo mistake in the Linode thread

A popular Linode community thread, "High CPU from kswapd0", has an answer that suggests dropping caches and rebooting. It also suggests setting swappiness so that swap is used only when RAM runs out, without giving a number. Its commands include this line:

$ sudo echo 1 > /proc/sys/vm/drop_caches

A later commenter reported that after following the steps, the processes still used over 100% CPU. The line has a second problem: run as a normal user, it does not work. The shell handles the > redirection before it runs the command. So your own unprivileged shell tries to open /proc/sys/vm/drop_caches for writing, and only echo runs under sudo. The shell is refused with Permission denied, and nothing is written. Either of these forms does the write as root:

echo 1 | sudo tee /proc/sys/vm/drop_caches
sudo sh -c 'echo 1 > /proc/sys/vm/drop_caches'

In the first, tee runs under sudo and opens the file itself. In the second, the whole command line, redirection included, runs inside a root shell. The same rule applies to any root-owned file you write from a normal account. That is why the swap section above uses sudo tee -a /etc/fstab. Use drop_caches for testing only.

The miner path: what to do when kswapd0 is fake

A fake kswapd0 is not the kernel, but the name alone does not prove an intruder has a shell on your server. Any locally started program can pick that name for itself, so first find out what the process is and who launched it. Record the evidence and look for the traces Outlaw leaves, as described below. Then check for SSH keys you did not add, cron entries you did not write and password logins you did not make. If that evidence shows a program nobody on your team started, treat the server as compromised. What to do if your VPS is hacked explains why you should not try to clean a compromised server. You cannot prove a rootkit is gone, because the tools that would prove it are tools the attacker controls. So the plan is to record evidence and stop the CPU burn. Then you rebuild.

Record the evidence first

PID=1234   # the fake kswapd0
ps -o pid,ppid,user,lstart,args -p $PID
sudo readlink /proc/$PID/exe
sudo sha256sum /proc/$PID/exe
sudo ls -la /proc/$PID/cwd/

These show when the process started, which file it runs, a hash of that file and the directory it runs from. sha256sum reads the binary through /proc, so it works even if the file on disk was deleted. Copy the output somewhere off the server.

Look for the files Outlaw leaves

sudo find /root /home -maxdepth 2 -name '.configrc*'
sudo grep -rn --include=authorized_keys mdrfckr /root /home
sudo ls -la /var/spool/cron/crontabs/
sudo crontab -l -u ubuntu
ls -la /etc/cron.d/

The find line looks for the hidden directory Securelist describes. The wildcard also catches a directory with a different number. The grep line searches every authorized_keys file under /root and /home for the attackers' key, and prints the file and line it finds. Replace ubuntu in the crontab line with each account on the server. Securelist lists cron among the techniques Outlaw uses but does not publish the entries. Read every crontab line and ask whether you wrote it.

Stop the process

sudo kill -9 $PID
pgrep -a kswapd0

pgrep should now show only the kernel's kswapd0. If the fake one comes back within minutes, something on the server is restarting it, and a crontab entry is the first place to look. Killing it stops the CPU use. It does not make the server safe, because the attacker's access is still there.

Rebuild, and keep the evidence

Treat every secret on the server as known to the attacker: SSH keys, database passwords, API tokens and any cloud credentials. If the compromised account could use sudo, assume the attacker had root. Build a fresh server and restore data from a backup you trust. Rotate every one of those secrets before the new server goes live.

Close the door: weak SSH passwords are the usual way in

Securelist says Outlaw "typically" gets in through weak or default SSH credentials. Before you rebuild, check whether a password login succeeded on the old server:

sudo journalctl -g "accepted password"

Any line it prints is a successful password login, with the account name and the source address. If you never log in with a password, every one of those lines is suspect.

Securelist's advice is to turn off password authentication, use key-based login, limit who can reach SSH, and add rate limiting with Fail2Ban or firewalld. On the new server, follow the SSH hardening guide for a VPS before you put anything on it. Then add Fail2Ban to block SSH brute-force attempts on Ubuntu 24.04. If your server uses firewalld instead of ufw, the firewalld basics guide shows how to limit SSH to the addresses you use.

FAQ

How do I know if kswapd0 is a virus or the real kernel thread?

Check four things for the kswapd0 PID. Its parent in /proc/<pid>/status should be kthreadd, which is normally PID 2. Its owner should be root. Its command line in /proc/<pid>/cmdline should be empty. sudo readlink /proc/<pid>/exe should give no target, because a kernel thread has no file on disk. A kswapd0 that runs a file in a home directory, or that belongs to a normal user, is a user-space program using the kernel thread's name. Linux lets any program set its own name, so that alone does not say what the program does. Securelist found the Outlaw botnet's XMRig miner using exactly this name, so check the file it runs and who started it.

Why is kswapd0 using high CPU when free shows memory left?

The free column is the wrong number to read. Look at available, which estimates how much memory new programs can get without swapping. If available is low, the server really is short of memory and kswapd0 is reclaiming pages. If available is high and kswapd0 still uses CPU, check that the busy process really is the kernel thread. Another program using the same name, such as a miner, would explain it.

Does adding a swap file fix kswapd0 high CPU?

It often helps. Swap lets the kernel move rarely used program memory out of RAM, so reclaim does not have to keep throwing away file pages and reading them back. It is not a full fix. If vmstat 1 5 still shows steady si and so after you add swap, the workload needs more RAM than the server has. Add memory, or cap the service that grows with systemd MemoryHigh and MemoryMax.

Should I drop caches or change swappiness to stop kswapd0?

No. Dropping caches frees only memory the kernel would reclaim anyway, and the kernel documentation says not to use it outside testing or debugging. Swappiness only changes which kind of page the kernel prefers to reclaim. Neither one reduces how much memory the workload needs, so kswapd0 comes back. If you do write to drop_caches for a test, use echo 1 | sudo tee /proc/sys/vm/drop_caches. The form sudo echo 1 > /proc/sys/vm/drop_caches fails with Permission denied for a normal user.

Was this article useful?

#kswapd0#memory#swap#malware#linux-troubleshooting#security