Back to blog
Linux & SRE September 15, 2026

Linux Network Packet Drops: Ethtool NIC Ring Buffers and SoftIRQ Kernel Tuning Guide

Automate WhatsApp Alerts
Start Free ➔

Your high-throughput Kafka broker, Redis cluster, or edge API proxy begins dropping connections during peak traffic bursts. Overall CPU usage across the 32-core server is sitting comfortably at $25%$, bandwidth utilization is well within your 10 Gbps / 25 Gbps physical link capacity, and ping reports zero loss.

Yet when you run ip -s link show, the RX dropped counter is incrementing by thousands of packets every second, application p99 tail latency jumps from $2\text{ ms}$ to $450\text{ ms}$, and downstream clients report random socket read timeouts:

$ ip -s link show dev eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP mode DEFAULT group default qlen 1000
    RX:  bytes packets errors dropped missed mcast
    14892014022 98401201     0   428901      0  1420

Why is Linux dropping packets when the host has massive spare CPU capacity and plenty of free memory?

In high-concurrency Linux networking, packet drops rarely stem from saturated physical bandwidth. They occur because of micro-burst descriptor exhaustion in the NIC hardware rings, imbalanced IRQ CPU affinity causing single-core SoftIRQ saturation, or NAPI poll budget starvation in the kernel network stack.

In this deep-dive guide, we trace the complete Linux receive path from wire to socket, decode vendor-specific ethtool -S counters, interpret /proc/net/softnet_stat hex metrics, balance multi-queue Receive Side Scaling (RSS), and size ring buffers mathematically.


1. Where Packets Actually Disappear: The Linux Ingress Stack

To stop packet drops, you must identify the exact subsystem where the kernel discarded the frame.

                  THE LINUX INGRESS PACKET LIFECYCLE

   Physical Wire / Fiber (Ethernet Frames arrive)
               │
               ▼
   [ 1. Physical Layer & MAC ] ──► Errors? Check CRC / FCS / Cable
               │
               ▼
   [ 2. NIC RX Descriptor Ring ] ──► Full? 💥 DROP (rx_missed_errors / rx_fifo_errors)
               │ (Direct Memory Access via DMA to Host RAM)
               ▼
   [ 3. Hardware Interrupt (Hard IRQ) ] ──► Signals CPU to schedule NAPI poll
               │
               ▼
   [ 4. NET_RX_SOFTIRQ & NAPI Poll ] ──► CPU at 100% or Budget Exhausted?
               │                         💥 DROP (/proc/net/softnet_stat time_squeeze)
               ▼
   [ 5. Kernel Netdev Backlog Queue ] ──► Full? 💥 DROP (netdev_max_backlog)
               │
               ▼
   [ 6. Linux Protocol Stack (IP/TCP/UDP) ] ──► Netfilter/iptables / routing
               │
               ▼
   [ 7. Socket Receive Buffer (SO_RCVBUF) ] ──► App slow? 💥 DROP (RcvbufErrors)
               │
               ▼
   [ 8. Application recv() / read() ]

Drop Layer Taxonomy

Drop LayerPrimary Symptom CounterUnderlying MechanismRoot Cause
NIC Descriptor Ringrx_missed_errors, rx_no_buffer, rx_fifo_errorsHardware descriptor ring is $100%$ full; DMA engine cannot write to RAM.Ingress traffic burst exceeded ring depth ($R_{ ext{required}}$) before CPU drained it.
SoftIRQ / NAPI Budgettime_squeeze in /proc/net/softnet_statNAPI loop reached netdev_budget or netdev_budget_usecs ceiling before clearing queue.Single CPU core handling IRQs saturated; NAPI poll budget too small.
Kernel Netdev Backlogdropped in /proc/net/softnet_statSoftware input queue between driver and IP stack is full.net.core.netdev_max_backlog reached during extreme packet-per-second (PPS) bursts.
Socket Receive BufferRcvbufErrors in nstat, Recv-Q high in ssSocket buffer (SO_RCVBUF) is full; TCP advertises win 0; UDP drops datagrams.Application reader blocked in user space; thread pool starvation.

2. Packets Per Second (PPS) vs Bandwidth

A common engineering mistake is monitoring only Megabits per second (Mbps) rather than Packets per second (PPS).

$$ ext{PPS} = rac{ ext{Throughput (Bytes/sec)}}{ ext{Average Packet Size (Bytes)}}$$

Scenario A: Large 1500-Byte MTU Payloads (Video Streaming / Big Data)
10 Gbps Stream ──► ~820,000 PPS (Light CPU Interrupt Load)

Scenario B: Small 64-Byte Payloads (DNS, VoIP, Gaming, Microservices)
10 Gbps Stream ──► ~14,880,000 PPS (Extreme SoftIRQ & Descriptor Pressure!)

The Linux kernel networking stack incurs fixed per-packet overhead (allocating sk_buff metadata, executing routing lookups, running firewall rules, and handling descriptor DMA). A $1\text{ Gbps}$ flood of 64-byte packets will choke a 32-core server long before a $10\text{ Gbps}$ stream of 1500-byte jumbo frames causes a single drop.


3. Deep NIC Inspection with Ethtool

Before modifying kernel parameters, audit your network controller hardware, driver, and active descriptor ring capacities:

# 1. Identify Network Interface Controller (NIC) hardware and driver
ethtool -i eth0

# 2. Check negotiated physical link speed, duplex, and pause frames
ethtool eth0 | grep -E 'Speed|Duplex|Auto-negotiation|Link detected'

# 3. Inspect Current vs Maximum Supported Ring Buffer Sizes
ethtool -g eth0

Interpreting ethtool -g Output

$ ethtool -g eth0
Ring parameters for eth0:
Pre-set maximums:
RX:             4096
RX Mini:        0
RX Jumbo:       0
TX:             4096
Current hardware settings:
RX:             512
RX Mini:        0
RX Jumbo:       0
TX:             512

💡 THE SRE GAP: In the output above, the hardware supports a maximum of 4096 descriptors, but the Linux driver is currently running with only 512 descriptors! Any sudden traffic spike exceeding 512 packets during an interrupt servicing delay will immediately trigger hardware packet drops.

Enlarging NIC Ring Buffers

To maximize burst absorption capacity without dropping frames:

# Increase RX and TX hardware descriptor rings to maximum capacity
sudo ethtool -G eth0 rx 4096 tx 4096

4. Mathematical Model: Sizing the NIC Ring Buffer

A descriptor ring does not eliminate sustained overload—it acts as a shock absorber for micro-bursts during the interval where the CPU is temporarily executing other tasks.

To prevent drops, the ring size $R_{ ext{required}}$ must satisfy:

$$R_{ ext{required}} ge ext{PPS}{ ext{peak}} imes T{ ext{service gap}}$$

Where:

  • $ ext{PPS}_{ ext{peak}}$ = Peak packet ingress rate per second
  • $T_{ ext{service gap}}$ = Maximum scheduling latency before NAPI SoftIRQ polls the queue (typically $200\mu\text{s} - 1000\mu\text{s}$)

Example Calculation:

Consider a 25 Gbps interface handling a peak burst of $8,000,000\text{ PPS}$ distributed across 8 RSS queues ($1,000,000\text{ PPS per queue}$). If CPU context switching or interrupt coalescing causes a $2\text{ ms}$ service gap ($T_{ ext{service gap}} = 0.002\text{ s}$):

$$R_{ ext{required}} ge 1,000,000 imes 0.002 = mathbf{2,000 ext{ descriptors}}$$

If the queue ring size is set to the default of $512$ descriptors, the ring will overflow and drop packets within $512\mu\text{s}$! Setting the ring to $4096$ descriptors completely prevents micro-burst drops.


5. Decoding /proc/net/softnet_stat and time_squeeze

The kernel records per-CPU networking SoftIRQ processing metrics inside /proc/net/softnet_stat. Each line corresponds to one CPU core, represented in hexadecimal:

# Decode /proc/net/softnet_stat into human-readable integers per CPU
awk '{
  printf "CPU %-3d | Processed: %-10d | Dropped: %-8d | Time-Squeeze: %-8d
",
    NR-1, strtonum("0x"$1), strtonum("0x"$2), strtonum("0x"$3)
}' /proc/net/softnet_stat

Sample Output:

CPU 0   | Processed: 48920194   | Dropped: 0        | Time-Squeeze: 142091
CPU 1   | Processed: 1204       | Dropped: 0        | Time-Squeeze: 0
CPU 2   | Processed: 980        | Dropped: 0        | Time-Squeeze: 0
CPU 3   | Processed: 1150       | Dropped: 0        | Time-Squeeze: 0

Decoding the Metrics

  1. Processed (Column 1): Total number of network packets processed by NAPI on this CPU.
  2. Dropped (Column 2): Packets dropped because net.core.netdev_max_backlog was exceeded.
  3. Time-Squeeze (Column 3): The number of times the NAPI processing loop on this CPU reached its processing budget (netdev_budget) or time limit (netdev_budget_usecs) while packets were still waiting in the queue!

🚨 CRITICAL FINDING: In the output above, CPU 0 handled $48.9 ext{M}$ packets and hit time_squeeze 142,091 times, while CPUs 1–3 were completely idle! This confirms a severe Single-Core SoftIRQ Bottleneck.


6. Resolving the Single-Core SoftIRQ Bottleneck (RSS & IRQ Affinity)

When all NIC interrupts hit a single CPU core (typically CPU 0), that core runs at $100%$ SoftIRQ (si in top), dropping packets even if all other 31 cores are $99%$ idle.

                 SINGLE-CORE BOTTLENECK vs BALANCED RSS

  ❌ Unbalanced (All IRQs hitting CPU 0):
  10 Gbps Ingress ──► eth0 ──► CPU 0 (100% SoftIRQ) ──► 💥 PACKETS DROPPED!
                               CPU 1..31 (0% Idle)

  ✅ Balanced Receive Side Scaling (RSS across 8 Cores):
                     ┌──► Queue 0 ──► CPU 0 (12% SoftIRQ)
  10 Gbps Ingress ───┼──► Queue 1 ──► CPU 1 (12% SoftIRQ)
                     ├──► Queue 2 ──► CPU 2 (12% SoftIRQ)
                     └──► Queue 3..7 ──► CPU 3..7 (12% SoftIRQ) ──► ZERO LOSS

Step 1: Check Multi-Queue Hardware Channels

# Check available and combined hardware queues
ethtool -l eth0

If combined queues are fewer than available CPU cores, expand them:

sudo ethtool -L eth0 combined 8

Step 2: Distribute IRQs Across NUMA-Local Cores

Inspect active hardware interrupt assignments in /proc/interrupts:

grep -E 'eth0|mlx|ixgbe|ena' /proc/interrupts

Ensure irqbalance is active, or manually pin NIC queue interrupts to physical cores on the same NUMA node:

# Pin IRQ 48 to CPU core 2
echo "2" | sudo tee /proc/irq/48/smp_affinity_list

7. Kernel Network Backlog & SoftIRQ Sysctl Tuning

Once hardware rings and RSS queues are balanced, tune the Linux kernel network processing budget parameters:

# 1. Increase the maximum packets NAPI can poll in one SoftIRQ cycle (Default: 300)
sudo sysctl -w net.core.netdev_budget=600

# 2. Increase maximum time spent in SoftIRQ processing per cycle (Default: 2000us -> 8000us)
sudo sysctl -w net.core.netdev_budget_usecs=8000

# 3. Increase software input queue backlog for high PPS bursts (Default: 1000)
sudo sysctl -w net.core.netdev_max_backlog=10000

# 4. Increase global maximum socket receive buffer ceiling
sudo sysctl -w net.core.rmem_max=16777216
sudo sysctl -w net.core.wmem_max=16777216

Persisting Changes Across Reboots

Add these configurations to /etc/sysctl.d/99-network-performance.conf:

net.core.netdev_budget = 600
net.core.netdev_budget_usecs = 8000
net.core.netdev_max_backlog = 10000
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216

8. Automated Packet Drop Triage Shell Script

Save this copy-pasteable script to capture a complete ingress networking profile during an incident:

#!/usr/bin/env bash
# ==============================================================================
# Pingzoapp Linux Network Ingress & Packet Drop Auditor
# ==============================================================================
set -euo pipefail

IFACE="${1:-eth0}"

echo "======================================================================"
echo " 1. INTERFACE LINK STATUS & RING DESCRIPTORS ($IFACE)"
echo "======================================================================"
ethtool -i "$IFACE" | grep -E 'driver|version|firmware'
ethtool -g "$IFACE"

echo -e "
======================================================================"
echo " 2. INTERFACE HARDWARE CHANNELS & RSS QUEUES"
echo "======================================================================"
ethtool -l "$IFACE"

echo -e "
======================================================================"
echo " 3. PER-CPU SOFTIRQ & TIME-SQUEEZE AUDIT (/proc/net/softnet_stat)"
echo "======================================================================"
awk '{
  printf "CPU %-3d | Processed: %-10d | Dropped: %-8d | Time-Squeeze: %-8d
",
    NR-1, strtonum("0x"$1), strtonum("0x"$2), strtonum("0x"$3)
}' /proc/net/softnet_stat

echo -e "
======================================================================"
echo " 4. TOP 10 HARDWARE DROP & ERROR COUNTERS (ethtool -S)"
echo "======================================================================"
ethtool -S "$IFACE" | grep -Ei 'drop|miss|err|fifo|over|discards' | grep -v ': 0' | head -20 || echo "Zero hardware drop counters detected."

echo -e "
======================================================================"
echo " 5. PROTOCOL-LEVEL SOCKET ERRORS (nstat)"
echo "======================================================================"
nstat -az | grep -Ei 'TcpRetransSegs|UdpRcvbufErrors|UdpInErrors|TcpExtListenDrops' || true

9. SRE Production Health & Incident Threshold Matrix

Metric / CounterHealthy BaselineWarning ThresholdIncident TriggerActionable SRE Response
rx_dropped (Interface)$0 ext{ drops/sec}$$> 10 ext{ drops/sec}$$> 100 ext{ drops/sec}$Enlarge RX descriptor ring via ethtool -G.
time_squeeze Rate$0 ext{ / min}$IncreasingMonotonic climbIncrease netdev_budget and balance RSS IRQ affinity.
SoftIRQ CPU (si)$< 20%$ per core$20% - 60%$$> 80%$ on single coreDistribute queue IRQs across CPU cores via smp_affinity_list.
UdpRcvbufErrors$0$Increasing$> 50 ext{ / sec}$Increase net.core.rmem_max and application read buffer.

10. SRE Production Checklist for Network Performance

  • Maximized RX/TX Rings: ethtool -g verified; current hardware rings set to pre-set maximums ($4096$).
  • Multi-Queue RSS Configured: ethtool -l combined queues match available physical CPU cores on NUMA node.
  • IRQ Balance Active: Hardware interrupts evenly distributed across cores without cross-socket NUMA penalties.
  • Tuned Netdev Budgets: netdev_budget = 600 and netdev_max_backlog = 10000 configured in /etc/sysctl.d/.
  • Hardware Offloads Verified: Checksum offload, GRO, and TSO enabled via ethtool -k.
  • Continuous Telemetry: Prometheus scrapes node_network_receive_drop_total and node_softnet_dropped_total on 15s intervals.

Conclusion & Next Steps

Packet drops on Linux hosts are not caused by mysterious network gremlins—they are the deterministic result of buffer queues overflowing before the CPU can service them.

By inspecting ethtool -g to maximize hardware rings, eliminating single-core SoftIRQ bottlenecks through RSS affinity, and tuning kernel NAPI poll budgets, you can eliminate packet drops and achieve sub-millisecond tail latencies across your infrastructure.


Monitor End-to-End Latency & Packet Loss with Pingzoapp

When packet drops occur at the network layer, synthetic edge probes are your fastest indicator of service degradation.

With Pingzoapp, you get:

  • Sub-Minute Multi-Region Probing: Monitor TCP connect times, TLS handshake latencies, and packet loss from edge locations worldwide.
  • SLA & Downtime Calculators: Translate raw packet loss and latency spikes into actionable error budgets using our free SLA Calculator.
  • Instant Multi-Channel Alerts: Receive actionable escalations on WhatsApp, Telegram, SMS, Slack, and Discord before packet drops trigger customer outages.

👉 Start Monitoring Free with Pingzoapp and protect your applications against silent network degradation.

Zero-Code Uptime Alerts

Stop Finding Out About Outages from Angry Users

Get instant WhatsApp & Discord alerts the second your API, website, or server goes down. Setup in 30 seconds with 60-second checks.

WhatsApp & Discord 60-Second Checks Free Forever Plan
Try Pingzo Free

Know before your users do

Connect official WhatsApp notification channels, Discord webhooks, Telegram bots, and public status pages. Start in 30 seconds.

Create Free Monitor