Finding Which Process Caused a CPU Spike on a Linux Server
A CPU alert on a Linux server usually arrives after the spike is over. By the time you sign in, top shows an idle machine and nothing points at the process that held the CPU. Most monitoring tools record that the host crossed a threshold and nothing about which process did it. This post covers how to keep enough history on the server to name the process the next time it happens, with tools from the distribution's own repositories.
Confirm the spike in the system history
I'd check whether sysstat is already collecting before installing anything. Run sar -u with the alert's time window to read CPU use from today's data file.
sar -u -s 03:00:00 -e 03:30:00
For an earlier day, add -f with the data file for that day. The files are in /var/log/sysstat on Debian and Ubuntu and in /var/log/sa on RHEL and Fedora.
The %user, %system, and %iowait columns show what kind of work held the CPU. High %iowait with low %user points at storage before it points at a busy process. Run sar -q for the same window to see the run queue and the load average. sar records totals for the whole system, so it confirms the spike and its size but can't name the process.
Keep a per-process history with atop
Install atop from the distribution's repositories. Its logging service writes a sample of every process to /var/log/atop/atop_YYYYMMDD every 600 seconds by default. A spike that lasts two minutes gets averaged into a ten minute sample and can disappear from it, so shorten the interval. On Debian and Ubuntu the setting is LOGINTERVAL in /etc/default/atop.
sudo apt install atop sudo sed -i 's/^LOGINTERVAL=.*/LOGINTERVAL=60/' /etc/default/atop sudo systemctl restart atop
A 60 second interval makes each daily log larger. Check the size of /var/log/atop after the first full day on a server with a small disk.
Read the log at the time of the alert
After the next alert, open that day's log at the alert time with -r for the file and -b for the start time.
atop -r /var/log/atop/atop_20261005 -b 03:10
Press C to sort the process list by CPU, then press t to step forward one sample and T to step back. Press c to show the full command line of each process. The process at the top of the CPU column in the sample that covers the alert time is most likely the cause. The command line usually names the job, the library, or the backup set it was working on.
Match the time to scheduled work
A spike at the same minute every day or every week usually comes from scheduled work. Run systemctl list-timers --all and compare the LAST column with the alert time. Then check /etc/crontab, the files in /etc/cron.d, and each user's crontab with crontab -l -u followed by the user name.
Read the journal for the same window to see which units started and stopped.
journalctl --since "2026-10-05 03:05" --until "2026-10-05 03:20"
When a service or session scope stops, systemd writes a line with the unit name followed by "Consumed" and the CPU time it used. Filter the window for those lines to rank the units that finished during the spike.
journalctl --since "2026-10-05 03:05" --until "2026-10-05 03:20" | grep "CPU time"
For a unit that is still running, systemctl status followed by the unit name shows a CPU line with the total CPU time it has used since it started.
Watch the next one live
When the spike repeats on a schedule, watch it as it happens. Run systemd-cgtop -d 5 a minute before the expected time and note which service or scope stays at the top. Package managers and snap refreshes run as their own units, so snapd.service or a snap.* scope at the top points at a snap refresh. Run snap changes to find a change that stays in the Doing status.
A process that starts and exits between two samples doesn't appear in atop or systemd-cgtop. Record every process start during the window with execsnoop from the BCC tools. It's in the bpfcc-tools package on Debian and Ubuntu as execsnoop-bpfcc, and in the bcc-tools package on RHEL and Fedora under /usr/share/bcc/tools/execsnoop.
sudo execsnoop-bpfcc -T
The -T option adds a time column. A script that starts hundreds of short processes in the alert minute shows up as a run of lines with the same parent process ID.
Where ET Ducky shows it
I build ET Ducky, and its Linux agent reads process exec and exit, file open and close, and network connect and accept events from eBPF tracepoints. A process that starts and exits between two samples is still in that event record. Every alert gets an automatic root-cause analysis (cause, impact, mitigation, prevention, and confidence) backed by the kernel events that triggered it.
When several servers spike at the same time, you'd start a Multi-Agent Session across them, and it correlates the events from each host to find a shared cause or an outlier. You can also open a Live Session shell from the browser without the need for a VPN and run the atop and journalctl commands above on the host. If the process at the top is one you don't recognize, the built-in crypto-miner-heuristic rule evaluates the same kernel event stream on every managed agent.
Try it on one server
One Windows or Linux server on the Free query tier with your own AI key costs $5 a month.
See pricing