<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: EniyiSunucum</title>
    <description>The latest articles on DEV Community by EniyiSunucum (@eniyisunucum).</description>
    <link>https://dev.to/eniyisunucum</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4128830%2Fffb3d3f8-e637-4ae5-80f0-abce55799033.png</url>
      <title>DEV Community: EniyiSunucum</title>
      <link>https://dev.to/eniyisunucum</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/eniyisunucum"/>
    <language>en</language>
    <item>
      <title>CPU Looks Fine but the VDS Is Slow: A Linux Incident Runbook</title>
      <dc:creator>EniyiSunucum</dc:creator>
      <pubDate>Thu, 17 Sep 2026 00:17:29 +0000</pubDate>
      <link>https://dev.to/eniyisunucum/cpu-looks-fine-but-the-vds-is-slow-a-linux-incident-runbook-3iha</link>
      <guid>https://dev.to/eniyisunucum/cpu-looks-fine-but-the-vds-is-slow-a-linux-incident-runbook-3iha</guid>
      <description>&lt;p&gt;A user reports that an application is slow or intermittently unreachable. The monitoring dashboard shows 20% CPU, memory still available, and no obvious outage. It is tempting to answer, “the server looks healthy.”&lt;/p&gt;

&lt;p&gt;That answer is usually premature.&lt;/p&gt;

&lt;p&gt;A single CPU graph cannot tell you whether a Linux guest is waiting on storage, reclaiming memory, losing packets, stalling inside a cgroup, contending for hypervisor time, or following a degraded network path. The useful question is not “is CPU high?” but &lt;strong&gt;where did the request spend its time?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This runbook provides a repeatable way to answer that question on a Linux virtual server. It starts with low-risk evidence, separates guest-level faults from upstream faults, and produces a compact incident package you can hand to an application team or infrastructure provider.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Safety note:&lt;/strong&gt; The first stages below are read-only. Run load generators such as fio or iperf3 only during an approved window, against endpoints you control, and with explicit rate limits. Never benchmark a production filesystem or a third-party host without permission.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  1. Freeze the incident boundary
&lt;/h2&gt;

&lt;p&gt;Before collecting metrics, write down four facts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The exact start and end time in UTC.&lt;/li&gt;
&lt;li&gt;The affected hostname, IP, port, and protocol.&lt;/li&gt;
&lt;li&gt;Whether all clients were affected or only one location/ISP.&lt;/li&gt;
&lt;li&gt;One concrete symptom: timeout, connection reset, slow TTFB, packet loss, or high application latency.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Capture a small identity bundle:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; &lt;span class="nt"&gt;-Is&lt;/span&gt;
hostnamectl
&lt;span class="nb"&gt;uname&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt;
&lt;span class="nb"&gt;uptime
who&lt;/span&gt; &lt;span class="nt"&gt;-b&lt;/span&gt;
journalctl &lt;span class="nt"&gt;--list-boots&lt;/span&gt; | &lt;span class="nb"&gt;head&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This prevents a common failure: comparing a user report from 14:05 with metrics from 14:40, after the system has already recovered.&lt;/p&gt;

&lt;p&gt;If the problem is HTTP, record timings from both the server and an external client:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;time &lt;/span&gt;curl &lt;span class="nt"&gt;-sS&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /dev/null https://example.com/
curl &lt;span class="nt"&gt;-sS&lt;/span&gt; &lt;span class="nt"&gt;-D&lt;/span&gt; - &lt;span class="nt"&gt;-o&lt;/span&gt; /dev/null https://example.com/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Repeat the same request against localhost or the private service address when possible. Fast locally but slow externally points away from the application process and toward the proxy, firewall, network, or client path.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Read CPU as a scheduler, not a percentage
&lt;/h2&gt;

&lt;p&gt;Start with a short time series rather than a single snapshot:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vmstat 1 10
mpstat &lt;span class="nt"&gt;-P&lt;/span&gt; ALL 1 10
pidstat &lt;span class="nt"&gt;-u&lt;/span&gt; &lt;span class="nt"&gt;-w&lt;/span&gt; 1 10
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These commands are provided by procps and sysstat on most distributions.&lt;/p&gt;

&lt;p&gt;Pay attention to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;r&lt;/strong&gt; in vmstat: runnable tasks waiting for CPU.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;b&lt;/strong&gt;: tasks blocked, often on I/O.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;wa&lt;/strong&gt;: time waiting for I/O.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;st&lt;/strong&gt;: steal time, when the virtual CPU was ready but the hypervisor scheduled something else.&lt;/li&gt;
&lt;li&gt;Context switches and migrations in pidstat.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Low aggregate CPU does not rule out saturation. One single-threaded worker can max one vCPU while the average across eight vCPUs remains near 12.5%. Per-CPU output from mpstat exposes that pattern.&lt;/p&gt;

&lt;p&gt;Sustained steal time that correlates with latency is strong evidence of host-side contention, but one sample is not proof. Capture several intervals and correlate them with application timing.&lt;/p&gt;

&lt;p&gt;Linux Pressure Stall Information adds another useful view:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /proc/pressure/cpu
&lt;span class="nb"&gt;cat&lt;/span&gt; /proc/pressure/memory
&lt;span class="nb"&gt;cat&lt;/span&gt; /proc/pressure/io
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The “some” and “full” lines describe how much time tasks were delayed because a resource was unavailable. PSI is often more informative than utilization because it measures waiting experienced by workloads.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Distinguish available memory from memory pressure
&lt;/h2&gt;

&lt;p&gt;Linux deliberately uses free RAM for cache, so the “free” column alone is not an alarm. Look for reclaim, swap activity, and allocation failures:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;free &lt;span class="nt"&gt;-h&lt;/span&gt;
vmstat 1 10
&lt;span class="nb"&gt;cat&lt;/span&gt; /proc/pressure/memory
ps &lt;span class="nt"&gt;-eo&lt;/span&gt; pid,ppid,comm,%mem,rss,vsz &lt;span class="nt"&gt;--sort&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nt"&gt;-rss&lt;/span&gt; | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-20&lt;/span&gt;
journalctl &lt;span class="nt"&gt;-k&lt;/span&gt; &lt;span class="nt"&gt;--since&lt;/span&gt; &lt;span class="s1"&gt;'-30 min'&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-Ei&lt;/span&gt; &lt;span class="s1"&gt;'oom|out of memory|memory cgroup'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Interpret the evidence together:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Continuous &lt;strong&gt;si/so&lt;/strong&gt; in vmstat indicates active swapping.&lt;/li&gt;
&lt;li&gt;Rising memory PSI means tasks are stalling during reclaim.&lt;/li&gt;
&lt;li&gt;OOM or memory-cgroup messages explain sudden process restarts.&lt;/li&gt;
&lt;li&gt;A large page cache with low swap activity is usually normal.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Inside containers, host memory may look comfortable while a service is hitting its cgroup limit. On cgroup v2 systems, inspect the relevant scope under /sys/fs/cgroup and compare memory.current with memory.max. Do not assume the VM total is the service limit.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Check storage latency, not only disk usage
&lt;/h2&gt;

&lt;p&gt;A filesystem can have plenty of free space and still respond slowly. First rule out capacity and inode exhaustion:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;df&lt;/span&gt; &lt;span class="nt"&gt;-hT&lt;/span&gt;
&lt;span class="nb"&gt;df&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt;
lsblk &lt;span class="nt"&gt;-o&lt;/span&gt; NAME,TYPE,SIZE,FSTYPE,MOUNTPOINTS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then sample the block layer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;iostat &lt;span class="nt"&gt;-xz&lt;/span&gt; 1 10
pidstat &lt;span class="nt"&gt;-d&lt;/span&gt; 1 10
&lt;span class="nb"&gt;cat&lt;/span&gt; /proc/pressure/io
journalctl &lt;span class="nt"&gt;-k&lt;/span&gt; &lt;span class="nt"&gt;--since&lt;/span&gt; &lt;span class="s1"&gt;'-30 min'&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-Ei&lt;/span&gt; &lt;span class="s1"&gt;'I/O error|timeout|reset|nvme|blk_update'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Useful indicators include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;await&lt;/strong&gt;: average time for an I/O request to complete.&lt;/li&gt;
&lt;li&gt;Queue size: sustained queues suggest the device cannot keep up.&lt;/li&gt;
&lt;li&gt;I/O PSI: application-visible time lost waiting on storage.&lt;/li&gt;
&lt;li&gt;Per-process reads and writes from pidstat.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Treat %util carefully on modern virtual and parallel storage. A high value can be meaningful, but a low value does not guarantee low latency. The combination of await, queueing, PSI, and application timing is stronger than any one number.&lt;/p&gt;

&lt;p&gt;Avoid immediately running fio. A benchmark can turn a partial incident into a full outage and can contaminate the very evidence you are trying to preserve. If a controlled test is necessary, use a disposable file, direct I/O where appropriate, a fixed size, a runtime limit, and an agreed bandwidth or IOPS cap.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Inspect sockets and the network stack
&lt;/h2&gt;

&lt;p&gt;Begin at the guest boundary:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ss &lt;span class="nt"&gt;-s&lt;/span&gt;
ss &lt;span class="nt"&gt;-lntup&lt;/span&gt;
ss &lt;span class="nt"&gt;-tin&lt;/span&gt;
ip &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nb"&gt;link
&lt;/span&gt;nstat &lt;span class="nt"&gt;-az&lt;/span&gt;
sar &lt;span class="nt"&gt;-n&lt;/span&gt; DEV,TCP,ETCP 1 10
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Look for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Retransmissions increasing during the incident.&lt;/li&gt;
&lt;li&gt;Receive or transmit drops on the interface.&lt;/li&gt;
&lt;li&gt;A growing listen or SYN backlog.&lt;/li&gt;
&lt;li&gt;Many sockets stuck in SYN-SENT, SYN-RECV, or CLOSE-WAIT.&lt;/li&gt;
&lt;li&gt;TCP RTT and retransmission fields in ss -tin.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the interface counters are clean but the application times out, inspect each layer of the request path: resolver, firewall, reverse proxy, application listener, and upstream dependency.&lt;/p&gt;

&lt;p&gt;For DNS, compare authoritative and recursive answers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dig example.com A +noall +answer +stats
dig @1.1.1.1 example.com A +noall +answer +stats
resolvectl query example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A slow resolver can make an otherwise healthy service feel randomly slow, especially when caches expire.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Test the path without over-reading ICMP
&lt;/h2&gt;

&lt;p&gt;From a client that actually experienced the problem, collect a bounded path sample:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mtr &lt;span class="nt"&gt;-rwzc&lt;/span&gt; 50 server.example.com
tracepath server.example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the reverse direction when you control both endpoints. Internet paths are often asymmetric.&lt;/p&gt;

&lt;p&gt;Do not diagnose packet loss from one intermediate hop alone. Routers may rate-limit or deprioritize ICMP while continuing to forward application traffic normally. Loss becomes meaningful when it begins at a hop and persists through later hops, especially to the destination, and when it correlates with TCP or application symptoms.&lt;/p&gt;

&lt;p&gt;Use iperf3 only between systems you control. Start the server on one endpoint, choose a modest bandwidth cap for UDP tests, and stop if the link or production workload degrades. Throughput is not a substitute for application timing; it is one controlled data point.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Add external routing evidence
&lt;/h2&gt;

&lt;p&gt;When the guest looks healthy but multiple remote networks fail, check the route from outside your infrastructure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is the prefix visible from several public route collectors?&lt;/li&gt;
&lt;li&gt;Did the origin ASN change?&lt;/li&gt;
&lt;li&gt;Is the announcement covered by a valid RPKI Route Origin Authorization?&lt;/li&gt;
&lt;li&gt;Did the AS path change near the incident time?&lt;/li&gt;
&lt;li&gt;Is the problem limited to one region or upstream?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A route visible from one collector is not proof of global reachability. Compare more than one vantage point and record timestamps. This is especially important for regional hosting, where an upstream or peering issue may affect one country while local monitoring stays green.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Decide which boundary owns the next action
&lt;/h2&gt;

&lt;p&gt;Use the evidence to route the incident:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Evidence&lt;/th&gt;
&lt;th&gt;Most likely next owner&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;One process or one vCPU saturated&lt;/td&gt;
&lt;td&gt;Application/service team&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Swap, memory PSI, or cgroup limit&lt;/td&gt;
&lt;td&gt;Guest configuration/application team&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;High await, queueing, and I/O PSI&lt;/td&gt;
&lt;td&gt;Storage or infrastructure team&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sustained steal time with normal guest load&lt;/td&gt;
&lt;td&gt;Virtualization provider&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Interface drops or retransmits from the guest&lt;/td&gt;
&lt;td&gt;Guest networking or provider edge&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Clean guest, path degradation across several clients&lt;/td&gt;
&lt;td&gt;Network/upstream provider&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Local service fast, public hostname slow&lt;/td&gt;
&lt;td&gt;DNS, proxy, firewall, or network path&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This table is not a verdict. It tells you where the next discriminating test belongs.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Build an evidence package a provider can act on
&lt;/h2&gt;

&lt;p&gt;A useful escalation is short and reproducible. Include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;VM identifier and affected public IP.&lt;/li&gt;
&lt;li&gt;UTC incident window.&lt;/li&gt;
&lt;li&gt;Source locations or ISPs affected.&lt;/li&gt;
&lt;li&gt;Exact destination IP, port, and protocol.&lt;/li&gt;
&lt;li&gt;Ten to sixty seconds of vmstat, mpstat, iostat, PSI, and interface counters.&lt;/li&gt;
&lt;li&gt;Application timing from inside and outside.&lt;/li&gt;
&lt;li&gt;MTR from both directions when available.&lt;/li&gt;
&lt;li&gt;Public routing observations and timestamps.&lt;/li&gt;
&lt;li&gt;A clear request, such as “please check host scheduling for this VM between 14:02 and 14:09 UTC.”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Avoid screenshots without axes, “the network is slow,” or a five-megabyte log dump with no timeline. The goal is to let another engineer test a specific hypothesis.&lt;/p&gt;

&lt;h2&gt;
  
  
  10. Keep the runbook lightweight
&lt;/h2&gt;

&lt;p&gt;The best incident kit is the one already installed and practiced. A small baseline of sysstat, curl, dig, mtr, and journal access covers a large share of Linux performance incidents. Add persistent monitoring for PSI, steal time, disk latency, retransmissions, and application percentiles so the next investigation begins with history instead of guesswork.&lt;/p&gt;

&lt;p&gt;A &lt;a href="https://eniyisunucum.com/en/vds-server" rel="noopener noreferrer"&gt;VDS&lt;/a&gt; plan should make the CPU allocation, memory limit, storage type, network policy, and support boundary explicit. Those details determine which signals you can observe and which evidence your provider must supply.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final principle
&lt;/h2&gt;

&lt;p&gt;When CPU looks fine, do not jump straight to a benchmark or a provider ticket. Follow the request across boundaries:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;application → scheduler → memory → storage → socket → guest interface → network path → routing&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;At each boundary, collect one time-stamped signal that can falsify a hypothesis. That turns “the server is slow” into an incident another engineer can reproduce and resolve.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Disclosure: I work on Linux-based virtual server infrastructure and AS215068 at &lt;a href="https://eniyisunucum.com/en/" rel="noopener noreferrer"&gt;EniyiSunucum&lt;/a&gt;. This runbook is vendor-neutral and the commands apply to standard Linux environments.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devops</category>
      <category>linux</category>
      <category>performance</category>
      <category>networking</category>
    </item>
  </channel>
</rss>
