...

How to Properly Configure IRQ Balance in Linux: A Practical Guide

Under Linux, IRQ Balance controls the distribution of hardware interrupts across CPU cores, thereby determining whether network load is distributed evenly or whether individual cores are slowed down. I’ll show you how to use irqbalance effectively, when to switch to manual IRQ affinity, and which settings work best on servers with high network load really matter.

Key points

Before I go into detail, I’ll summarize the key decisions that have consistently helped me in projects with high I/O loads. I consider automatic distribution via irqbalance a good starting point; I measure its effectiveness and make selective adjustments. For deterministic workloads, I manually pin individual IRQs to specific cores and exclude the remaining CPUs from automatic distribution. I take NUMA proximity into account early on because it reduces latency and ensures throughput. With clear monitoring, I identify bottlenecks faster and resolve them without unnecessary Risks.

This list shows you what I pay special attention to when configuring the system:

  • Automatic First: Enable irqbalance, measure the effect
  • affinity Targeted: Pin critical IRQs, reduce jitter
  • Banned CPUs: Keep cores free for app threads
  • NUMA Note: Keep IRQs close to the memory node
  • Monitoring: Check /proc/interrupts and latencies

IRQ Basics Explained in a Nutshell

An interrupt request (IRQ) is a signal that hardware uses to pass work to the CPU, thereby interrupting a running task. If too many of these signals are sent to the same core, the load on that core increases and its response time slows down, while other cores remain idle; that’s exactly what I want to do with Distribution avoid. irqbalance dynamically distributes these IRQs across multiple cores and periodically assesses the system status. To do this, I first look at /proc/interrupts and check the columns to see how many IRQs are arriving per CPU. If individual columns become too overwhelming, I actively adjust them to reduce unnecessary Hotspots.

Automatic load balancing with irqbalance

On modern distributions, I start with the irqbalance service, which periodically adjusts IRQ distribution by default. I enable it manually with systemctl enable --now irqbalance and check the status before I take more drastic action; that way, I make use of the existing Automatic. Depending on the system, the configuration files are located in /etc/sysconfig/irqbalance or /etc/default/irqbalance, where I can rule out CPUs or IRQs. The variable is especially helpful IRQBALANCE_BANNED_CPUS as a 64-bit mask to reserve specific cores for applications. If you want to delve deeper into practical examples, you'll find a concise introduction to Network Performance, which I frequently refer to in workshops and incorporate into projects.

Implementing Manual IRQ Affinity Safely

If workloads are very sensitive to jitter or if certain cores need to remain exclusively available for user-space processes, I set IRQ affinity manually. To do this, I copy bitmasks to /proc/irq/IRQ NUMBER/smp_affinity and specify which cores an interrupt is allowed to run on; this makes it possible to plan Conduct. First, I determine the relevant IRQ numbers using grep in /proc/interrupts. For network devices, I often pin RX/TX queues to cores that are close to the app threads, while keeping other cores free. This short article provides a good explanation of the reasoning behind this approach IRQ Affinity Guide, which I regularly use as a starting point.

The following table shows common bitmasks and what they mean. I use these examples to set configurations quickly and with few errors, and then verify the effect using /proc/interrupts to verify.

Goal Sample Mask (hex) cores Comment
CPU0 only 0x1 0 Simple test, low dispersion
CPU1 Only 0x2 1 Disables IRQs on CPU0, reduces Interference
CPU0–CPU1 0x3 0–1 Spread across two cores, light Relief
CPU2–CPU3 0xC 2-3 Useful when 0–1 for app threads free stay
CPU0–CPU3 0xF 0–3 Wide distribution across 4 cores, mixes Load

Measurement: Reading /proc/interrupts Correctly

I open the file /proc/interrupts and I see one IRQ per row and the counters for each CPU per column; this immediately reveals any imbalances visible. If one column grows significantly faster than the others, the load concentrates there. I then check which driver is involved and whether RSS/RPS is already being distributed. In addition, I temporarily run irqbalance in the foreground with debug output to understand its decision-making process and avoid misjudgments. After every change, I check the counters again and measure latency under load so that I can verify the effects and avoid unnecessary Risks can avoid.

CPU Isolation and Banned Masks

I set IRQBALANCE_BANNED_CPUS, to consistently exclude certain cores from automatic allocation; this way, I free up resources for app threads. In newer setups, I also use IRQBALANCE_BANNED_IRQS, if individual devices are to run independently on a single core; this reduces interference with sensitive Workloads. In low-latency scenarios, I specifically disable irqbalance and statically pin IRQs so that no reallocation interferes. Anyone who wants to understand the CPU assignment of interrupt handling in more detail will find useful background information on the Interrupt Handling on servers. The key is to measure first, then set the parameters, and check the results again to avoid surprises in the Operation to avoid.

NUMA Considerations and Proximity

On NUMA systems, I make sure to route IRQs, whenever possible, to the cores of the NUMA node where the relevant data is stored; this reduces latency and increases Throughput. I combine this with CPU affinity for the application so that threads and interrupts run locally together. irqbalance works well on NUMA, but I fine-tune it with banned masks as needed. It’s critical not to spread the load across nodes if it can be kept local anyway. Maintaining this proximity ensures consistent response times and conserves valuable Cache-Resources.

Network Intensive: RSS, RPS/RFS, and XPS

Before I fine-tune IRQ masks, I check NIC features such as RSS, as well as kernel mechanisms such as RPS/RFS and XPS; they have a significant impact on packet distribution. RSS already distributes queue interrupts across multiple cores, while RPS/RFS shape processing within the kernel and XPS shapes the transmission paths; this avoids unnecessary Hotspots. I fine-tune these mechanisms with my IRQ strategy to ensure they don't work against each other. When the queues, IRQ affinities, and app affinities are properly aligned, network I/O runs much more smoothly. After that, I measure performance again under real-world load before I proceed with further Steps set.

MSI-X, Multi-Queue, and Clean Queue Layout

Many 10–100G NICs use MSI-X and provide separate interrupt vectors for each RX/TX queue. I first check with ethtool -l eth0 (number of channels) and /proc/interrupts, how many queues are actually active and what their names are (e.g.,. eth0-TxRx-0, eth0-TxRx-1). The goal is to adjust the number of queues to the number of cores in use per NUMA node and to pin them deterministically. With ethtool -L eth0 combined N I set the number of queues; then I organize the resulting IRQs using smp_affinity appropriate cores. I make sure to place RX/TX pairs from the same queue on the same core—or at least the same socket—so that Cache Locality takes effect. Important: I check changes to the queue number and affinity directly in /proc/interrupts and with a quick load test (pps/throughput) before I continue optimizing.

Interrupt Coalescence and NAPI Budget

Coalescence values affect the effectiveness of my IRQ strategy, especially at high packet rates. With ethtool -c eth0 I can see if rx-usecs and rx-frames are set. Increasing the coalescence reduces the number of IRQs per second and saves CPU resources, but increases latency and jitter. I make adjustments carefully: small steps, measuring each time (p95/p99 latency and CPU load). On the sender side, tx-usecs analog. In addition, I scale the NAPI behavior via net.core.netdev_budget and net.core.netdev_budget_usecs, when NET_RX begins to pile up in SoftIRQs. If the number of drops increases in /proc/net/softnet_stat, I increase the budget on a trial basis or distribute RX queues more consistently; if system latency becomes a problem, I scale back. I consider GRO/LRO and TSO/GSO in conjunction with one another: excessive aggregation reduces IRQ load but can cause latency spikes—I balance this out based on the application profile.

Reading SoftIRQs Transparent

In addition to hard IRQs, I determine the load on soft IRQs. With cat /proc/softirqs I observe NET_RX and NET_TX per CPU; if individual columns dominate, too much work ends up in ksoftirqd threads. A top -H Quickly show me which ksoftirqd/N Seeds are a burden. I'm taking a closer look with perf top or short perf record Run to identify hotspots in the driver or stack processing. If ksoftirqd threads become active (instead of immediate processing in the IRQ context), latency often increases significantly; I respond by improving queue distribution, increasing the NAPI budget, or specifically pinning the affected ksoftirqd threads to a CPU via taskset -pc. Important: I document these changes because they have subtle effects, and I may need to roll them back quickly if necessary.

Making the Most of SMT/Hyper-Threading and Topology

With SMT enabled, I share a physical core with two logical CPUs. I check the sibling relationships via lscpu -e and /sys/devices/system/cpu/cpuX/topology/thread_siblings_list. For latency-critical paths, I avoid placing the app thread and its associated IRQ on the same physical core (different SMT threads); they compete for execution units and caches. I prefer pairs where, for example, an app thread runs on CPU2 and the associated RX queue runs on CPU3 (different physical core, same NUMA node). If SMT disrupts consistency, I plan instead with fewer but exclusive physical cores and avoid unstable Interference.

Virtualization: KVM, vhost, and SR-IOV

In virtualized environments, I treat the host and guest separately. On the host, I distribute physical NIC IRQs evenly across the cores of the corresponding NUMA node. If the guest uses virtio-net, additional IRQs are generated for vhost threads; I detect them in /proc/interrupts and consistently pin vhost workers to the queues of the physical NIC. At the guest level, I also set RSS/XPS and IRQ affinities, provided the virtio driver offers multiple queues. With SR-IOV, it’s worth assigning each guest one or more VFs with their own MSI-X vectors and pinning them within the guest; this isolation improves latency and predictability. I follow a clear scheme: assign the guest’s vCPUs to dedicated pCPUs, place the associated IRQs on nearby cores, and keep the emulator/vhost threads separate from computationally intensive application threads—this keeps the data path plannable.

CPU Frequency, C-States, and NOHZ Tuning

IRQ latencies suffer when cores enter deep C-states or are clocked aggressively. For sensitive workloads, I set the CPU governor to performance (cpupower frequency-set -g performance) and reduce deep C-states via boot or driver options to limit wake-up times. On heavily loaded servers, this often has a more positive effect than any fine-tuning of affinities. In very demanding latency profiles, I supplement nohz_full= and rcu_nocbs= for isolated cores, so that tick timers and RCU callbacks don’t interfere with each other; I deliberately define the housekeeping CPUs separately. However, I test these changes separately because they can have side effects on scheduling and power consumption. The key point remains: I need to carefully compare the measured values before and after the change; otherwise, I’ll be in the dark when it comes to Optimizations in the dark.

Systemd, Cgroups, and App Isolation

In addition to IRQ pinning, I isolate app threads using Cgroups and systemd affinity. Via CPUAffinity= In unit files and the CPU controllers (cgroup v2), I assign fixed cores to services. This prevents the scheduler from scheduling threads on the CPUs I have reserved for IRQs. In container environments, I set cpuset.cpus and check cpuset.cpus.effective, so that resource commitments actually take effect. Important: IRQBALANCE_BANNED_CPUS only controls where irqbalance does not distribute; kernel threads such as ksoftirqd continue to follow the scheduler. For hard isolation, therefore, I need a combination of IRQ affinities, CPU affinities for the services, and, if necessary, isolated kernels. This keeps the data path and the application clearly separated, and the Load does not mix uncontrollably.

Common Mistakes and Solutions

I never disable `irqbalance` across the board without knowing the load patterns; otherwise, IRQs quickly become concentrated on just a few cores. It’s equally undesirable to open all cores to all IRQs, even though sensitive threads are exclusive Resources need. Another mistake: not testing changes in isolation and not measuring their effects; this leaves it unclear what actually helps. I also take Hyper-Threading pairs into account: the app thread and its associated IRQ should not share the same physical core. I document every step and create rollback points so that, if problems arise, I can quickly revert to the last good Return to the configuration.

Practical Checklist for Servers

I always start with a baseline: enable irqbalance, monitor system load, observe /proc/interrupts, and measure latencies; only then do I adjust the settings. In the second step, I conclude with IRQBALANCE_BANNED_CPUS I select the cores that should be reserved for app threads; this prevents unnecessary IRQ interruptions. Then I pin critical IRQs via smp_affinity I limit the system to a few, well-chosen cores and ensure NUMA proximity. Then I check RSS/RPS/RFS and XPS, as well as the NIC’s offloading options, to distribute the workload effectively. Finally, I run tests under production load, compare metrics, and keep only those changes that are proven to work.

Configuration Files and systemd Commands

I'll activate the service using systemctl enable --now irqbalance and check using systemctl status irqbalance the duration; that's how I set the Service definitely ready. In /etc/sysconfig/irqbalance or /etc/default/irqbalance I set IRQBALANCE_BANNED_CPUS as well as (optional) IRQBALANCE_BANNED_IRQS. I'll implement the changes using systemctl restart irqbalance and at the same time monitor the counters in /proc/interrupts. For testing, I use irqbalance's foreground mode to monitor decisions in real time. Only once I understand the behavior do I permanently write the adjustments into the Configuration.

When I disable irqbalance

In real-time setups or for extremely latency-sensitive applications, I stop `irqbalance` and statically pin IRQs so that no reallocation interferes. I isolate the cores for these workloads and deliberately let traffic IRQs run on other cores; this way, application threads remain plannable. This approach is also worthwhile even in strictly isolated tenant environments because it reduces interference between VMs or containers. If I encounter drivers that don’t work well with the automatic mode, I exclude their IRQs via a banned list. As soon as the load patterns become more variable again, I reactivate irqbalance and verify the effect with fresh Measured values.

Briefly summarized

I start with irqbalance, measure the effect, and make selective adjustments instead of blindly making changes everywhere; this way, I maintain an overview of the system and Transparency. For sensitive workloads, I pin appropriate IRQs, isolate cores for applications, and take NUMA proximity into account. Using banned masks, I control where irqbalance is allowed to operate and prevent unintended shifts. I regularly check /proc/interrupts, latency, and throughput, so that changes are supported by robust evidence. Those who take this approach fully leverage the potential of IRQ Balance and keep servers noticeably stable under network load reactive.

Current articles