{"id":1542,"date":"2026-10-29T22:44:41","date_gmt":"2026-10-29T22:44:41","guid":{"rendered":"https:\/\/virtualserversvps.com\/blog\/?p=1542"},"modified":"2026-10-29T22:44:41","modified_gmt":"2026-10-29T22:44:41","slug":"diagnosing-vps-softirq-network-bottleneck","status":"publish","type":"post","link":"https:\/\/virtualserversvps.com\/blog\/diagnosing-vps-softirq-network-bottleneck\/","title":{"rendered":"Diagnosing a Softirq Network Bottleneck on a Single-vCPU VPS"},"content":{"rendered":"<p class=\"wp-block-paragraph\">A single-vCPU VPS can be perfectly healthy by every CPU metric and still drop packets under load. The usual cause is that the receive path is processed by softirqs on whichever CPU the driver raised them on, and on a one-vCPU guest there is exactly one place for that work to happen. This is not the same problem as a full softirq backlog; it is about where the work executes, how many queues exist, and whether the one CPU can keep up. This article covers the measurements that prove the bottleneck is in the softirq path, and the knobs that move it.<\/p>\n\n\n<h2 class=\"wp-block-heading\">Hardirq vs Softirq: The Split That Matters<\/h2>\n\n\n<p class=\"wp-block-paragraph\">When a frame arrives, the device raises an interrupt. The hardirq handler runs with interrupts disabled and is supposed to do the minimum: acknowledge the device, mask further interrupts, and schedule the deferred part. The deferred part is a softirq \u2014 NAPI polling in the <code>NET_RX<\/code> vector \u2014 which runs with interrupts enabled, either right after the hardirq returns or in the <code>ksoftirqd<\/code> thread when load is high.<\/p>\n\n<p class=\"wp-block-paragraph\">The same received packet can therefore be charged to <code>%irq<\/code>, <code>%softirq<\/code>, or to a kernel thread. The measurement question is how much CPU the receive path consumes per unit of traffic, and whether that work competes with your application threads.<\/p>\n\n\n<pre class=\"wp-block-code\"><code>mpstat -P ALL 2 5\ngrep -E '^(cpu|intr)' \/proc\/stat\nps -eo pid,comm,pcpu | grep -E 'ksoftirqd|kworker'\ncat \/proc\/interrupts | head -20<\/code><\/pre>\n\n\n<p class=\"wp-block-paragraph\">On a one-vCPU guest, <code>%softirq<\/code> climbing while <code>%sys<\/code> stays low is the signature to look for. Because there is only one CPU, <code>ksoftirqd\/0<\/code> competing directly with your application threads is expected under sustained traffic, and it shows up as reduced application throughput rather than as a network error.<\/p>\n\n\n<h2 class=\"wp-block-heading\">Confirm the Bottleneck Exists<\/h2>\n\n\n<p class=\"wp-block-paragraph\">Before tuning, prove that the ceiling is on the receive path and not on the sender, the link, or your application. The clean test is to generate traffic from a separate host and compare the rate at which packets arrive with the rate at which your process consumes them. If RX packets rise while your application&#8217;s request rate plateaus, and <code>%softirq<\/code> saturates on the single vCPU, the receive path is the limit.<\/p>\n\n\n<pre class=\"wp-block-code\"><code># on the VPS: receive-side counters, sampled\nawk 'NR&gt;2 {print $1, &quot;rx_pkts=&quot;$3, &quot;rx_drop=&quot;$4, &quot;rx_err=&quot;$5}' \/proc\/net\/dev\nss -lntu | head\nnstat -az | grep -E 'InErrors|InDrops|RcvbufErrors|ListenDrops|TCPRcvQDrop'<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Where the Interrupts Land<\/h2>\n\n\n<p class=\"wp-block-paragraph\">A virtio NIC in a KVM guest can present one or more virtqueues. Each queue can be bound to a vCPU, and in multi-queue mode each virtqueue&#8217;s interrupt has its own affinity. The mapping is visible in two places: the queue directories under the device, and the interrupt affinity mask in <code>\/proc\/interrupts<\/code>. Reading both is the fastest way to see whether all RX work is funnelled onto one CPU.<\/p>\n\n\n<pre class=\"wp-block-code\"><code>ls \/sys\/class\/net\/eth0\/queues\/\nethtool -l eth0\nethtool -S eth0 | head -30\nfor irq in $(awk '\/virtio\/{print $1}' \/proc\/interrupts | tr -d :); do\n  echo &quot;$irq: $(cat \/proc\/irq\/$irq\/smp_affinity_list)&quot;\ndone<\/code><\/pre>\n\n\n<p class=\"wp-block-paragraph\">On a one-vCPU instance every mask will read <code>0<\/code>, and that is the point: there is nowhere else to move the work. Interrupt affinity tuning is therefore not available, and what remains is reducing the cost per packet or adding a CPU. On instances with two or more vCPUs, spreading queue interrupts across vCPUs is the first and most effective fix.<\/p>\n\n\n<h2 class=\"wp-block-heading\">RPS and RFS: Spreading Work Without Hardware Queues<\/h2>\n\n\n<p class=\"wp-block-paragraph\">Receive Packet Steering (RPS) emulates multiple receive queues in software by hashing flows across CPUs and queueing the deferred processing on a per-CPU backlog. Receive Flow Steering (RFS) goes further and steers a flow&#8217;s processing to the CPU where the application thread that owns the socket is running, improving cache locality.<\/p>\n\n<p class=\"wp-block-paragraph\">On a single-vCPU guest neither does anything useful \u2014 there is one CPU to steer to \u2014 but the configuration still matters when you later resize. On a 2- or 4-vCPU plan, RPS is often the difference between one saturated core and even distribution, particularly when the virtio device exposes only one receive queue.<\/p>\n\n\n<pre class=\"wp-block-code\"><code># RPS: bitmask of allowed CPUs, one value per rx queue\necho f &gt; \/sys\/class\/net\/eth0\/queues\/rx-0\/rps_cpus     # CPUs 0-3\n# RFS: global flow table size and per-queue flow limit\nsysctl -w net.core.rps_sock_flow_entries=32768\necho 4096 &gt; \/sys\/class\/net\/eth0\/queues\/rx-0\/rps_flow_cnt\n\n# verify the packets actually got steered\ncat \/proc\/softirqs | grep NET_RX\nmpstat -P ALL 2 5<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">When One Queue Is the Ceiling<\/h2>\n\n\n<p class=\"wp-block-paragraph\">The definitive sign of a single-queue ceiling is that throughput stops scaling while packets keep arriving. In a guest this appears as: one virtqueue active in <code>ethtool -S<\/code>, RX drops rising, <code>%softirq<\/code> pinned near the CPU&#8217;s capacity, and application throughput flat. A useful check is to compare packets-per-second at two different frame sizes; if small frames collapse the rate far more than large ones, you are limited by per-packet processing cost rather than by bandwidth.<\/p>\n\n\n<pre class=\"wp-block-code\"><code>ethtool -g eth0\nethtool -G eth0 rx 1024 tx 1024\n# batching indicators\nethtool -S eth0 | grep -iE 'packets|drops|queue'\ncat \/proc\/softirqs | awk '\/NET_RX\/{print}'<\/code><\/pre>\n\n\n<p class=\"wp-block-paragraph\">A larger ring absorbs bursts, but it does not reduce steady-state CPU per packet: per-packet cost is dominated by descriptor handling, skb allocation and the stack walk, which is why a flood of small messages hits a hard ceiling that large frames never reach. Larger rings buy burst headroom; more vCPUs buy packets per second.<\/p>\n\n\n<h2 class=\"wp-block-heading\">Putting the Evidence Together<\/h2>\n\n\n<p class=\"wp-block-paragraph\">A convincing diagnosis measures five things over one window: the per-CPU <code>NET_RX<\/code> delta from <code>\/proc\/softirqs<\/code>, the <code>%softirq<\/code> share from <code>mpstat<\/code>, the drop counters from <code>nstat<\/code>, the queue count from <code>ethtool -l<\/code>, and the application&#8217;s achieved request rate. When the softirq delta grows linearly with arriving packets while the request rate plateaus, the receive path is the bottleneck and no other explanation fits.<\/p>\n\n<p class=\"wp-block-paragraph\">From there, the fix depends on what the platform allows. More vCPUs plus queue interrupts spread across them is the real solution; RPS helps when the driver exposes a single queue; coalescing and larger rings buy headroom. When none of the kernel levers is available \u2014 no multi-queue, no coalescing control, one vCPU \u2014 the constraint is the plan, and the <a href=\"https:\/\/virtualserversvps.com\/\">VPS plans with more vCPUs and dedicated network queues<\/a> are the practical next step. Some of the apparent network limit is really storage or scheduler contention on the same core, covered in the <a href=\"https:\/\/virtualserversvps.com\/blog\/linux-vps-cpu-governor-swappiness-io-scheduler-tuning\">kernel tuning guide for governors, swappiness and I\/O schedulers<\/a>.<\/p>\n\n\n<h2 class=\"wp-block-heading\">Quick Diagnostic Sequence<\/h2>\n\n\n\n<ul class=\"wp-block-list\"><li><code>mpstat -P ALL<\/code> \u2014 is <code>%softirq<\/code> the dominant term on the single vCPU?<\/li><li><code>\/proc\/softirqs<\/code> \u2014 does <code>NET_RX<\/code> grow on one CPU only?<\/li><li><code>nstat<\/code> and <code>\/proc\/net\/dev<\/code> \u2014 are drops at the socket, queue, or device level?<\/li><li><code>ethtool -l<\/code> and <code>\/proc\/interrupts<\/code> \u2014 how many queues, and where are their interrupts?<\/li><li>Vary frame size \u2014 does small-packet rate collapse relative to large?<\/li><li>Enable RPS\/RFS only after confirming the bottleneck is receive-side CPU.<\/li><\/ul>\n\n\n<p class=\"wp-block-paragraph\">On a single-vCPU VPS the softirq path is the only place receive work can run, so it saturates first and saturates quietly. Measuring where packets are counted, where interrupts land, and how the drop counters move is enough to prove the bottleneck without guesswork \u2014 and to know whether it is fixable from the guest or only by changing the plan.<\/p>","protected":false},"excerpt":{"rendered":"<p>A single-vCPU VPS can be perfectly healthy by every CPU metric and still drop packets under load. The usual cause is that the receive path is processed by softirqs on&#8230;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"iawp_total_views":0,"footnotes":""},"categories":[1],"tags":[],"class_list":["post-1542","post","type-post","status-publish","format-standard","hentry","category-vps-guides-tutorials"],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v26.1 (Yoast SEO v26.1) - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Diagnosing a Softirq Network Bottleneck on a Single-vCPU VPS - Virtual Servers VPS Blog<\/title>\n<meta name=\"description\" content=\"A single-vCPU VPS can be perfectly healthy by every CPU metric and still drop packets under load. The usual cause is that the receive path is processed by\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/virtualserversvps.com\/blog\/diagnosing-vps-softirq-network-bottleneck\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Diagnosing a Softirq Network Bottleneck on a Single-vCPU VPS\" \/>\n<meta property=\"og:description\" content=\"Diagnosing a Softirq Network Bottleneck on a Single-vCPU VPS\" \/>\n<meta property=\"og:url\" content=\"https:\/\/virtualserversvps.com\/blog\/diagnosing-vps-softirq-network-bottleneck\/\" \/>\n<meta property=\"og:site_name\" content=\"Virtual Servers VPS Blog\" \/>\n<meta property=\"article:published_time\" content=\"2026-10-29T22:44:41+00:00\" \/>\n<meta name=\"author\" content=\"Virtual-Servers-Vps-Editor\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Virtual-Servers-Vps-Editor\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"6 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\/\/virtualserversvps.com\/blog\/diagnosing-vps-softirq-network-bottleneck\/\",\"url\":\"https:\/\/virtualserversvps.com\/blog\/diagnosing-vps-softirq-network-bottleneck\/\",\"name\":\"Diagnosing a Softirq Network Bottleneck on a Single-vCPU VPS - Virtual Servers VPS Blog\",\"isPartOf\":{\"@id\":\"https:\/\/virtualserversvps.com\/blog\/#website\"},\"datePublished\":\"2026-10-29T22:44:41+00:00\",\"author\":{\"@id\":\"https:\/\/virtualserversvps.com\/blog\/#\/schema\/person\/82a299a8284a66ff49f97c74684724a0\"},\"description\":\"A single-vCPU VPS can be perfectly healthy by every CPU metric and still drop packets under load. The usual cause is that the receive path is processed by\",\"breadcrumb\":{\"@id\":\"https:\/\/virtualserversvps.com\/blog\/diagnosing-vps-softirq-network-bottleneck\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/virtualserversvps.com\/blog\/diagnosing-vps-softirq-network-bottleneck\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/virtualserversvps.com\/blog\/diagnosing-vps-softirq-network-bottleneck\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/virtualserversvps.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Diagnosing a Softirq Network Bottleneck on a Single-vCPU VPS\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/virtualserversvps.com\/blog\/#website\",\"url\":\"https:\/\/virtualserversvps.com\/blog\/\",\"name\":\"Virtual Servers VPS Blog\",\"description\":\"\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/virtualserversvps.com\/blog\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Person\",\"@id\":\"https:\/\/virtualserversvps.com\/blog\/#\/schema\/person\/82a299a8284a66ff49f97c74684724a0\",\"name\":\"Virtual-Servers-Vps-Editor\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/virtualserversvps.com\/blog\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/d820b15f1cd028e97610d9adf536df7be5cb6423869967037d468d5355fa003f?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/d820b15f1cd028e97610d9adf536df7be5cb6423869967037d468d5355fa003f?s=96&d=mm&r=g\",\"caption\":\"Virtual-Servers-Vps-Editor\"},\"sameAs\":[\"https:\/\/virtualserversvps.com\/blog\"],\"url\":\"https:\/\/virtualserversvps.com\/blog\/author\/virtualserversvps\/\"}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Diagnosing a Softirq Network Bottleneck on a Single-vCPU VPS - Virtual Servers VPS Blog","description":"A single-vCPU VPS can be perfectly healthy by every CPU metric and still drop packets under load. The usual cause is that the receive path is processed by","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/virtualserversvps.com\/blog\/diagnosing-vps-softirq-network-bottleneck\/","og_locale":"en_US","og_type":"article","og_title":"Diagnosing a Softirq Network Bottleneck on a Single-vCPU VPS","og_description":"Diagnosing a Softirq Network Bottleneck on a Single-vCPU VPS","og_url":"https:\/\/virtualserversvps.com\/blog\/diagnosing-vps-softirq-network-bottleneck\/","og_site_name":"Virtual Servers VPS Blog","article_published_time":"2026-10-29T22:44:41+00:00","author":"Virtual-Servers-Vps-Editor","twitter_card":"summary_large_image","twitter_misc":{"Written by":"Virtual-Servers-Vps-Editor","Est. reading time":"6 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/virtualserversvps.com\/blog\/diagnosing-vps-softirq-network-bottleneck\/","url":"https:\/\/virtualserversvps.com\/blog\/diagnosing-vps-softirq-network-bottleneck\/","name":"Diagnosing a Softirq Network Bottleneck on a Single-vCPU VPS - Virtual Servers VPS Blog","isPartOf":{"@id":"https:\/\/virtualserversvps.com\/blog\/#website"},"datePublished":"2026-10-29T22:44:41+00:00","author":{"@id":"https:\/\/virtualserversvps.com\/blog\/#\/schema\/person\/82a299a8284a66ff49f97c74684724a0"},"description":"A single-vCPU VPS can be perfectly healthy by every CPU metric and still drop packets under load. The usual cause is that the receive path is processed by","breadcrumb":{"@id":"https:\/\/virtualserversvps.com\/blog\/diagnosing-vps-softirq-network-bottleneck\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/virtualserversvps.com\/blog\/diagnosing-vps-softirq-network-bottleneck\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/virtualserversvps.com\/blog\/diagnosing-vps-softirq-network-bottleneck\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/virtualserversvps.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Diagnosing a Softirq Network Bottleneck on a Single-vCPU VPS"}]},{"@type":"WebSite","@id":"https:\/\/virtualserversvps.com\/blog\/#website","url":"https:\/\/virtualserversvps.com\/blog\/","name":"Virtual Servers VPS Blog","description":"","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/virtualserversvps.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Person","@id":"https:\/\/virtualserversvps.com\/blog\/#\/schema\/person\/82a299a8284a66ff49f97c74684724a0","name":"Virtual-Servers-Vps-Editor","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/virtualserversvps.com\/blog\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/d820b15f1cd028e97610d9adf536df7be5cb6423869967037d468d5355fa003f?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/d820b15f1cd028e97610d9adf536df7be5cb6423869967037d468d5355fa003f?s=96&d=mm&r=g","caption":"Virtual-Servers-Vps-Editor"},"sameAs":["https:\/\/virtualserversvps.com\/blog"],"url":"https:\/\/virtualserversvps.com\/blog\/author\/virtualserversvps\/"}]}},"_links":{"self":[{"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/posts\/1542","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/comments?post=1542"}],"version-history":[{"count":1,"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/posts\/1542\/revisions"}],"predecessor-version":[{"id":1543,"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/posts\/1542\/revisions\/1543"}],"wp:attachment":[{"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/media?parent=1542"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/categories?post=1542"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/tags?post=1542"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}