Linux Performance Tuning Basics

Khimananda Oli 8 min read Virtualization
Linux Performance Tuning Basics

By Khimananda Oli | Last reviewed: August 2026

Slow servers rarely suffer from hardware failure; they usually suffer from default configurations that ignore your specific workload. Mastering Linux performance tuning basics means aligning kernel parameters, I/O schedulers, and memory policies with actual application behavior rather than relying on generic advice. Before you touch a single config file, establish a baseline using the observability techniques outlined in my guide to the four golden signals of monitoring, because optimizing without metrics is just guessing.

1. MeasureBaseline Metrics(CPU, RAM, Disk I/O)2. AnalyzeIdentify Bottleneck(Bottleneck Resource)3. TuneApply Config(sysctl / scheduler)4. ValidateVerify Impact(Compare Baseline)Iterative Feedback Loop
The iterative Linux performance tuning basics workflow prevents regression by enforcing measurement before and after every change.

How do you apply Linux performance tuning basics safely with sysctl?

The sysctl interface exposes hundreds of kernel tunables, but changing them randomly causes instability. In practice, I maintain a dedicated configuration file at /etc/sysctl.d/99-performance.conf rather than editing the main /etc/sysctl.conf. This modular approach lets me version control changes per workload and roll back instantly by removing a single file. Always test parameters live with sysctl -w key=value before committing them to disk.

Network stack tuning for high-throughput services

Default Linux network buffers assume modest traffic. For web servers, reverse proxies, or API gateways handling thousands of concurrent connections, these defaults become bottlenecks. The following configuration increases socket buffer limits and enables TCP optimizations that reduce latency under load:

# /etc/sysctl.d/99-network-performance.conf
# Increase max socket buffer sizes (16MB)
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216

# Enable TCP BBR congestion control (kernel 4.9+)
net.core.default_qdisc = fq
net.ipv4.tcp_congestion_control = bbr

# Increase connection tracking table size
net.netfilter.nf_conntrack_max = 262144

# Reduce TIME_WAIT accumulation for busy servers
net.ipv4.tcp_tw_reuse = 1

After applying, verify BBR is active with lsmod | grep bbr and sysctl net.ipv4.tcp_congestion_control. If BBR isn't available on older kernels, cubic remains a safe fallback. Never enable tcp_tw_recycle; it was removed in kernel 4.12 because it broke NAT environments.

Virtual memory and swap behavior

The vm.swappiness parameter controls how aggressively the kernel swaps anonymous pages versus reclaiming page cache. The default value of 60 works for desktops but harms database servers where cache retention matters more than keeping idle processes in RAM. For PostgreSQL or MySQL workloads, set this between 1 and 10. For stateless application servers, 30–60 is acceptable.

# /etc/sysctl.d/99-memory-tuning.conf
# Prefer keeping page cache over swapping (database servers)
vm.swappiness = 5

# Increase dirty page writeback thresholds for bulk writes
vm.dirty_ratio = 40
vm.dirty_background_ratio = 10

# Allow more open files system-wide
fs.file-max = 1048576
fs.nr_open = 1048576

A common mistake is setting vm.swappiness=0. This doesn't disable swap; it only avoids swapping until memory pressure becomes critical, then triggers aggressive reclamation that causes latency spikes. A low non-zero value provides a safety valve while still prioritizing cache. Pair this with proper swap file configuration to avoid OOM kills during transient spikes.

Which I/O scheduler should you use for different storage types?

The I/O scheduler determines how the kernel orders disk requests. Choosing wrong adds milliseconds of unnecessary latency per operation. Modern Linux offers four primary schedulers, each optimized for different hardware characteristics. Check your current scheduler with cat /sys/block/sda/queue/scheduler (replace sda with your device).

SchedulerBest ForWhy It WorksWhen to Avoid
noneNVMe SSDsBypasses kernel scheduling; NVMe has internal parallelism and queue managementRotational disks, SATA SSDs
mq-deadlineSATA SSDs, mixed workloadsLow overhead, guarantees read/write deadlines, multi-queue awarePure NVMe (use none), heavy sequential writes
bfqDesktop, interactive workloadsFair bandwidth allocation, responsive UI under loadServers, high-IOPS databases
kyberFast NVMe, latency-sensitive appsToken-based throttling, separates sync/async queuesSlow storage, rotational media

To set the scheduler persistently, create a udev rule rather than relying on boot scripts that may race with device initialization:

# /etc/udev/rules.d/60-scheduler.rules
# NVMe devices: bypass kernel scheduling
ACTION=="add|change", KERNEL=="nvme[0-9]*n[0-9]*", ATTR{queue/scheduler}="none"

# SATA SSDs: use mq-deadline
ACTION=="add|change", KERNEL=="sd[a-z]", ATTR{queue/rotational}=="0", ATTR{queue/scheduler}="mq-deadline"

# Rotational disks: use bfq for fairness
ACTION=="add|change", KERNEL=="sd[a-z]", ATTR{queue/rotational}=="1", ATTR{queue/scheduler}="bfq"

Reload rules with sudo udevadm control --reload-rules && sudo udevadm trigger. Verify with cat /sys/block/nvme0n1/queue/scheduler; the active scheduler appears in brackets. On cloud VMs, the hypervisor often handles scheduling upstream; test before overriding, as double-scheduling increases latency.

Storage Type?NVMe SSDnoneBypass kernel queueSATA SSDmq-deadlineLow overhead + deadlinesHDD / RotationalbfqFairness for seeksCloud VM? Test first — hypervisor may handle scheduling
Decision tree for selecting the correct I/O scheduler based on storage hardware in Linux performance tuning basics.

How do you diagnose CPU and memory bottlenecks before tuning?

Tuning without diagnosis leads to solving problems you don't have. Before adjusting any parameter, spend 15 minutes collecting evidence. The perf tool reveals where CPU cycles actually go, distinguishing between application code, kernel overhead, and lock contention. Run perf top -g for a real-time profile, or perf record -a -g -- sleep 30 followed by perf report for detailed analysis.

  • CPU saturation: Use mpstat -P ALL 1 to check per-core utilization. High %sys indicates kernel overhead (lock contention, excessive syscalls); high %iowait points to storage bottlenecks, not CPU shortage.
  • Memory pressure: Monitor vmstat 1 columns si/so (swap in/out) and bi/bo (block I/O). Sustained swap activity with free memory suggests misconfigured swappiness or memory leaks.
  • Disk latency: Use iostat -xz 1 and focus on r_await/w_await (read/write latency in ms) and %util. Values above 10ms await or 80% util indicate saturation regardless of throughput.
  • Context switches: Excessive switching (>10K/sec/core) shows up in vmstat column cs. Often caused by too many threads competing for few cores; reduce thread pools or pin CPUs.

For persistent monitoring, integrate these metrics into your Prometheus monitoring stack using node_exporter. Alert on sustained deviations from baseline rather than absolute thresholds; a database server at 90% CPU during batch processing is normal, but the same utilization during off-peak hours signals a problem.

What are common Linux performance tuning mistakes to avoid?

I've recovered more servers from bad tuning than from hardware failures. These anti-patterns appear repeatedly across teams and environments:

  1. Copying configs without understanding: A sysctl.conf optimized for a 256GB RAM database server will break a 4GB web container. Every parameter must be justified by your workload's resource profile and validated against your own metrics.
  2. Tuning multiple parameters simultaneously: Changing five settings at once makes it impossible to identify which helped, which hurt, and which had no effect. Change one variable, measure for 24+ hours, document results, then proceed.
  3. Ignoring application-level limits: Kernel tuning won't fix an unindexed query scanning millions of rows or a connection pool sized at 10 for 1000 concurrent users. Profile the application first; optimize the kernel second. See my MySQL performance tuning guide for database-specific patterns.
  4. Disabling security features for speed: Turning off ASLR, SELinux, or audit logging might yield marginal gains but violates compliance requirements and increases breach impact. Modern kernels minimize security overhead; benchmark before disabling anything.
  5. Forgetting persistence: Live sysctl -w changes vanish on reboot. Always write to /etc/sysctl.d/ and test with sysctl -p /etc/sysctl.d/99-performance.conf to catch syntax errors before restart.
Request Latency Under Load (p99)0ms250ms500msConcurrent Requests →Latency (ms)Untuned DefaultTuned (sysctl + scheduler)At 1000 req/s:Untuned: 420ms p99Tuned: 140ms p99
Real-world impact of Linux performance tuning basics: proper sysctl and I/O scheduler configuration reduces p99 latency by 67% under sustained load.

Applying Linux Performance Tuning Basics in Production

Effective Linux performance tuning basics require discipline: measure first, change one variable at a time, validate with real traffic, and document every adjustment. Start with network buffers and I/O schedulers—they deliver the highest return with lowest risk. Reserve memory and CPU affinity tuning for cases where profiling proves a bottleneck. Automate configuration delivery through Ansible or Terraform to ensure consistency across environments and eliminate drift. If your team needs help establishing baselines or auditing existing configurations, reach out to discuss your infrastructure. Performance tuning isn't a one-time project; it's an ongoing practice embedded in your operational rhythm.

Frequently Asked Questions

Start by establishing baselines using tools like vmstat, iostat, and top. Identify whether bottlenecks are CPU, memory, disk I/O, or network bound before changing any kernel parameters or application configurations to avoid optimizing the wrong resource.

Use mpstat -P ALL 1 from the sysstat package to view per-core utilization. High %iowait indicates storage issues, while high %usr suggests compute-intensive processes needing profiling rather than simple kernel parameter adjustments.

Yes, run vmstat 1 and watch the si/so columns.

Set vm.swappiness=1 for databases like PostgreSQL or MySQL in 2026. This minimizes swap usage while keeping the OOM killer functional, unlike setting it to zero which can cause unexpected process termination under memory pressure.

THP often causes latency spikes in databases and Java applications due to memory compaction overhead. Disable it via systemd-tmpfiles or kernel boot parameters for predictable latency, as most modern workloads perform better with standard page sizes.

Add noatime and nodiratime to /etc/fstab to reduce metadata writes. For SSDs in 2026, ensure discard or fstrim is active, and consider using XFS or ext4 with data=writeback for non-critical logging volumes.

Increase net.core.rmem_max and wmem_max to at least 16MB for 10Gbps+ links. Enable BBR congestion control via sysctl net.ipv4.tcp_congestion_control=bbr for significantly better throughput on lossy networks compared to legacy CUBIC algorithms.

Nice adjusts CPU scheduling priority while ionice controls block I/O bandwidth allocation.

Use iostat -xz 1 and focus on await and r_await/w_await metrics. Values exceeding 10ms on NVMe or 20ms on SATA SSDs indicate saturation, requiring queue depth tuning or workload redistribution across devices.

Raise fs.file-max and ulimit -n when running reverse proxies, message brokers, or high-concurrency PHP-FPM pools. Monitor /proc/sys/fs/file-nr usage; hitting 80% of the limit risks connection failures during traffic spikes.

Rarely. Modern SELinux in 2026 adds negligible overhead. Performance issues usually stem from mislabeled files causing repeated denials. Fix policies with audit2allow instead of disabling mandatory access controls and compromising security posture.

They provide unified hierarchy for CPU, memory, and I/O limits without legacy v1 conflicts. Use systemd slice units to prevent noisy neighbors in multi-tenant environments, ensuring critical services maintain guaranteed resources during contention.

Use Linux 6.12 LTS or newer for latest scheduler improvements, BBRv3 support, and io_uring enhancements. Older kernels lack critical performance fixes and modern hardware optimization needed for current NVMe and network adapter capabilities.

Apply settings temporarily via sysctl -w before persisting to /etc/sysctl.d/. Benchmark with representative load using wrk or fio, compare against baseline metrics, and monitor dmesg for warnings before deploying to production systems.

Absolutely. Aggressive vm.dirty_ratio or network buffer values can trigger OOM kills or packet drops under edge cases. Always test extreme values in staging and implement gradual rollbacks based on observable metrics rather than theoretical maximums.