LINUXOR.SK ... open source notes ...

Linux kernel performance tuning with sysctl

category: configz · date: 2026-09-11 · author: LALA
noteThis is a performance-tuning reference, not a universal configuration. It is based on an older high-performance sysctl.conf and updated for modern Linux kernels. Values should be validated with measurements on the actual hardware, kernel, network path and application.

Kernel tuning should follow this order:

1. Measure the bottleneck. 2. Change one related group of parameters. 3. Benchmark again. 4. Keep only settings that produce a measurable improvement. 5. Record the workload assumptions together with the configuration.

A practical layout is:

output 2 lines
/etc/sysctl.d/70-performance-common.conf
/etc/sysctl.d/80-performance-role.conf

Apply with:

bash
$ sysctl --system

Common performance profile

Create /etc/sysctl.d/70-performance-common.conf:

ini
# -----------------------------------------------------------------------------
# Virtual memory
# -----------------------------------------------------------------------------

# Keep most application memory resident while still allowing the kernel to
# reclaim very cold anonymous pages when useful.
vm.swappiness = 10

# Start background writeback relatively early.
vm.dirty_background_ratio = 5

# Avoid the very large writeback bursts caused by the historical value of 60.
vm.dirty_ratio = 20

# Retain inode/dentry caches longer than the default on filesystem-heavy hosts.
vm.vfs_cache_pressure = 50

# -----------------------------------------------------------------------------
# File descriptor capacity
# -----------------------------------------------------------------------------

# System-wide file-handle ceiling.
# Per-process RLIMIT_NOFILE / systemd LimitNOFILE must also be configured.
fs.file-max = 2097152

# -----------------------------------------------------------------------------
# Socket buffer ceilings
# -----------------------------------------------------------------------------

# Useful starting point for high-bandwidth or higher-RTT servers.
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216

# Keep default per-socket allocation moderate.
net.core.rmem_default = 262144
net.core.wmem_default = 262144

# -----------------------------------------------------------------------------
# TCP auto-tuning
# -----------------------------------------------------------------------------

# min / default / max
net.ipv4.tcp_rmem = 4096 131072 16777216
net.ipv4.tcp_wmem = 4096 16384 16777216

net.ipv4.tcp_window_scaling = 1

# -----------------------------------------------------------------------------
# Connection queues
# -----------------------------------------------------------------------------

# Modern kernels commonly default to 4096; keep explicit for server roles.
net.core.somaxconn = 4096

# Increased SYN queue for bursty server workloads.
net.ipv4.tcp_max_syn_backlog = 16384

# Increased device receive backlog for traffic bursts.
net.core.netdev_max_backlog = 16384

# -----------------------------------------------------------------------------
# Outbound connection capacity
# -----------------------------------------------------------------------------

# Useful for reverse proxies and clients opening many outbound connections.
net.ipv4.ip_local_port_range = 10240 65535

# -----------------------------------------------------------------------------
# TCP behavior
# -----------------------------------------------------------------------------

net.ipv4.tcp_timestamps = 1
net.ipv4.tcp_sack = 1
net.ipv4.tcp_tw_reuse = 1

# Keep current kernel behavior unless measurements show a specific problem.
net.ipv4.tcp_fin_timeout = 60
net.ipv4.tcp_no_metrics_save = 0

High connection-rate web server or reverse proxy

For NGINX, HAProxy, Envoy, Apache event MPM, API gateways and similar workloads:

ini
net.ipv4.tcp_max_syn_backlog = 32768
net.core.somaxconn = 16384
net.core.netdev_max_backlog = 32768
net.ipv4.ip_local_port_range = 10240 65535

Also check application-side limits:

output 5 lines
listen() backlog
worker/process count
RLIMIT_NOFILE
systemd LimitNOFILE
application connection limits

Measure queue overflows:

bash
$ nstat -az | grep -E 'ListenOverflows|ListenDrops|Syncookies'
$ ss -lnt

Do not enable this as a generic optimization:

ini
net.ipv4.tcp_abort_on_overflow = 1

The kernel documentation warns that it can harm clients. Increase queue capacity or improve the application's accept rate first.

High-throughput load balancer or network server

For an L4 load balancer, forwarding appliance, proxy or high-throughput gateway:

ini
net.core.somaxconn = 16384
net.ipv4.tcp_max_syn_backlog = 32768
net.core.netdev_max_backlog = 32768

net.core.rmem_max = 33554432
net.core.wmem_max = 33554432

net.ipv4.tcp_rmem = 4096 131072 33554432
net.ipv4.tcp_wmem = 4096 16384 33554432

net.ipv4.ip_local_port_range = 10240 65535

Floating / virtual IP addresses

High-availability load balancers may also need:

ini
net.ipv4.ip_nonlocal_bind = 1

This lets the service bind a VIP before the address is present locally.

IP forwarding

Enable only for designs that actually forward packets:

ini
net.ipv4.ip_forward = 1

For IPv6 forwarding:

ini
net.ipv6.conf.all.forwarding = 1

A userspace reverse proxy normally does not need IP forwarding.

Bandwidth-delay product

TCP buffer sizing should be related to bandwidth-delay product:

output 1 line
BDP = bandwidth x round-trip time

Example for a 10 Gbit/s path with 20 ms RTT:

output 3 lines
10,000,000,000 bit/s x 0.020 s
= 200,000,000 bits
= approximately 25 MB

A socket window materially below the BDP can limit single-flow throughput.

This is why 16-32 MiB maximum buffers may be reasonable for WAN-facing high-bandwidth systems while providing little benefit on ordinary low-latency LAN servers.

Congestion control

Inspect what the kernel provides:

bash
$ sysctl net.ipv4.tcp_available_congestion_control
$ sysctl net.ipv4.tcp_congestion_control

cubic is a strong general-purpose default.

If the installed kernel provides BBR, benchmark it under the actual workload before enabling it:

ini
net.ipv4.tcp_congestion_control = bbr

Also inspect the queuing discipline:

bash
$ sysctl net.core.default_qdisc
$ tc qdisc show

Congestion control should be evaluated using realistic RTT, packet loss and bandwidth conditions.

Low-latency networking

Modern Linux supports NAPI busy polling. It trades CPU cycles and power consumption for lower packet-delivery latency.

Global examples:

ini
# Example only - microseconds.
# net.core.busy_read = 50
# net.core.busy_poll = 50

Do not put these in a generic profile. Current kernels support per-socket and per-epoll controls, which are preferable where the application supports them.

Measure:

output 5 lines
p50 / p95 / p99 latency
CPU utilization
softirq utilization
IRQ distribution
power consumption

Database or storage-heavy server

A conservative latency-oriented starting point is:

ini
vm.swappiness = 1
vm.dirty_background_ratio = 5
vm.dirty_ratio = 15
vm.vfs_cache_pressure = 50

fs.file-max = 2097152

Vendor recommendations should take precedence.

Do not raise:

ini
vm.max_map_count

without an application requirement. The historical value 2097152 is not a universal performance optimization.

Applications such as Elasticsearch/OpenSearch may explicitly require a higher value, but that belongs in the application profile rather than a generic Linux performance file.

File descriptor capacity

The historical:

ini
fs.file-max = 2097152

is still a reasonable ceiling for connection-heavy servers.

However, the service also needs an adequate process limit:

bash
$ ulimit -n
$ cat /proc/<PID>/limits

For systemd:

ini
[Service]
LimitNOFILE=1048576

A higher descriptor limit does not by itself improve performance; it only prevents the descriptor ceiling from becoming the bottleneck.

NIC and CPU tuning

For high packet rates, NIC/CPU placement often matters more than sysctl values.

Inspect queue and ring configuration:

bash
$ ethtool -l eth0
$ ethtool -g eth0
$ ethtool -S eth0

Inspect interrupt distribution:

bash
$ grep -i eth0 /proc/interrupts

Inspect kernel receive processing:

bash
$ cat /proc/net/softnet_stat
$ ip -s -s link show dev eth0

Evaluate:

output 8 lines
RSS
RPS / RFS
IRQ affinity
NUMA locality
NIC ring sizes
GRO / GSO / TSO
CPU frequency governor
application worker-to-CPU placement

Measurement checklist

Before and after changing sysctl values:

bash
$ ss -s
$ nstat -az
$ cat /proc/net/sockstat
$ cat /proc/net/softnet_stat
$ vmstat 1
$ iostat -xz 1
$ sar -n DEV,TCP,ETCP 1

Important indicators:

output 11 lines
TCP ListenOverflows / ListenDrops
SYN retransmissions
TCP retransmissions
TIME-WAIT count
socket memory consumption
softnet drops
NIC RX/TX drops
disk await / queue depth
dirty-memory writeback spikes
CPU softirq utilization
p50 / p95 / p99 application latency

Suggested profiles

General server

ini
vm.swappiness = 10
vm.dirty_background_ratio = 5
vm.dirty_ratio = 20
vm.vfs_cache_pressure = 50

fs.file-max = 2097152

net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.core.rmem_default = 262144
net.core.wmem_default = 262144

net.ipv4.tcp_rmem = 4096 131072 16777216
net.ipv4.tcp_wmem = 4096 16384 16777216

net.core.somaxconn = 4096
net.ipv4.tcp_max_syn_backlog = 16384
net.core.netdev_max_backlog = 16384

net.ipv4.ip_local_port_range = 10240 65535

net.ipv4.tcp_window_scaling = 1
net.ipv4.tcp_timestamps = 1
net.ipv4.tcp_sack = 1
net.ipv4.tcp_tw_reuse = 1

High-throughput proxy / load balancer

ini
fs.file-max = 2097152

net.core.rmem_max = 33554432
net.core.wmem_max = 33554432

net.ipv4.tcp_rmem = 4096 131072 33554432
net.ipv4.tcp_wmem = 4096 16384 33554432

net.core.somaxconn = 16384
net.ipv4.tcp_max_syn_backlog = 32768
net.core.netdev_max_backlog = 32768

net.ipv4.ip_local_port_range = 10240 65535

net.ipv4.tcp_window_scaling = 1
net.ipv4.tcp_timestamps = 1
net.ipv4.tcp_sack = 1
net.ipv4.tcp_tw_reuse = 1

# HA/VIP deployments only:
# net.ipv4.ip_nonlocal_bind = 1

# L3/L4 forwarding only:
# net.ipv4.ip_forward = 1

Database / storage starting point

ini
vm.swappiness = 1
vm.dirty_background_ratio = 5
vm.dirty_ratio = 15
vm.vfs_cache_pressure = 50

fs.file-max = 2097152

References

← configz