RHEL 9 Performance Tuning: tuned, sysctl, and CPU/Memory Optimization
Performance Tuning Isn't Optional in Production
A default RHEL 9 installation is tuned for broad compatibility, not for your specific workload. That means you're leaving CPU frequency scaling on conservative settings, the VM page cache consuming RAM your database could use, and network buffers sized for 2005 hardware.
This guide covers the RHEL 9 tooling for workload-specific tuning: tuned for profile-based configuration, sysctl for kernel parameters, and targeted adjustments for database, web, and low-latency workloads.
tuned: Profile-Based Tuning
tuned is the RHEL performance tuning daemon. It applies a set of kernel and system settings based on a profile and monitors the system, dynamically adjusting when needed.
# Install and enable tuned
sudo dnf install tuned -y
sudo systemctl enable --now tuned
# Check current profile
tuned-adm active
# List available profiles
tuned-adm list
Built-in Profiles
| Profile | Use Case |
|---|---|
throughput-performance | High-throughput servers (databases, batch) |
latency-performance | Low-latency workloads, trading systems |
network-throughput | High-bandwidth network applications |
network-latency | Low-latency networking |
virtual-guest | VMs running inside a hypervisor |
virtual-host | Hypervisor hosts |
balanced | Default — balance of performance and power |
powersave | Minimize energy consumption |
# Apply a profile
sudo tuned-adm profile throughput-performance
# Recommend a profile based on system detection
tuned-adm recommend
# Verify active tuning
tuned-adm active
tuned-adm verify
Custom tuned Profiles
For workload-specific tuning beyond what built-in profiles provide:
# Create a custom profile
sudo mkdir /etc/tuned/my-db-server
sudo cat > /etc/tuned/my-db-server/tuned.conf << 'EOF'
[main]
summary=Custom profile for PostgreSQL/MySQL database servers
include=throughput-performance
[vm]
transparent_hugepages=never
[sysctl]
vm.swappiness=10
vm.dirty_ratio=15
vm.dirty_background_ratio=5
net.core.somaxconn=65535
net.ipv4.tcp_max_syn_backlog=4096
[disk]
# readahead appropriate for random I/O workloads
readahead=256
EOF
sudo tuned-adm profile my-db-server
CPU Frequency Scaling
Modern CPUs throttle frequency to save power. For latency-sensitive workloads, you want consistent maximum frequency.
# Check available governors
cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_available_governors
# Check current governor
cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor
# Set performance governor on all CPUs (via tuned or manually)
for cpu in /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor; do
echo performance | sudo tee "$cpu"
done
# Or use cpupower (from kernel-tools)
sudo dnf install kernel-tools -y
sudo cpupower frequency-set -g performance
# Verify
cpupower frequency-info | grep "current policy"
The latency-performance and throughput-performance tuned profiles both set the performance governor automatically.
Processor C-States
For very low-latency requirements, disable deeper C-states that add wake-up latency:
# Check C-state info
cpupower idle-info
# Disable C-states deeper than C1 (via kernel parameter at boot)
# Add to /etc/default/grub GRUB_CMDLINE_LINUX:
# intel_idle.max_cstate=1
# (or amd_idle.max_cstate=1 for AMD)
sudo grub2-mkconfig -o /boot/grub2/grub.cfg
Key sysctl Parameters
Edit /etc/sysctl.d/99-performance.conf:
Network Tuning
# Increase socket buffers for high-throughput
net.core.rmem_max = 134217728
net.core.wmem_max = 134217728
net.ipv4.tcp_rmem = 4096 87380 134217728
net.ipv4.tcp_wmem = 4096 65536 134217728
# Connection handling
net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 4096
net.core.netdev_max_backlog = 5000
# TIME_WAIT tuning
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 15
# Enable BBR congestion control (RHEL 9 supports it)
net.core.default_qdisc = fq
net.ipv4.tcp_congestion_control = bbr
Memory / VM
# Prefer RAM over swap
vm.swappiness = 10
# Dirty page write-back (tune for your storage latency)
vm.dirty_ratio = 20
vm.dirty_background_ratio = 5
# Increase virtual memory areas for JVM/Elasticsearch
vm.max_map_count = 262144
# File descriptor limits
fs.file-max = 1000000
Apply immediately:
sudo sysctl --system
sysctl net.ipv4.tcp_congestion_control
Transparent Huge Pages
THP (Transparent Huge Pages) reduces TLB pressure for some workloads but causes latency spikes in databases (MySQL, PostgreSQL, Redis, MongoDB all recommend disabling it).
# Check current state
cat /sys/kernel/mm/transparent_hugepage/enabled
# Disable THP immediately
echo never | sudo tee /sys/kernel/mm/transparent_hugepage/enabled
echo never | sudo tee /sys/kernel/mm/transparent_hugepage/defrag
# Persist via tuned.conf (shown above) or via rc.local / systemd unit
For HPC or in-memory workloads (JVM heap), THP may improve performance — test with your specific workload.
NUMA Topology Awareness
On multi-socket servers, NUMA (Non-Uniform Memory Access) topology significantly affects performance. Memory access to a remote NUMA node is 2-3x slower.
# Install NUMA tools
sudo dnf install numactl -y
# Check NUMA topology
numactl --hardware
# Run a process on a specific NUMA node
numactl --cpunodebind=0 --membind=0 -- ./my-app
# Check NUMA stats (memory allocations per node)
numastat
numastat -p <pid>
# For databases: pin to a NUMA node
# PostgreSQL: start with numactl
# Redis: set numa_node in config if supported
NUMA Balancing
RHEL 9 includes automatic NUMA balancing (numad). For latency-critical workloads, pin manually and disable auto-balancing:
# Disable automatic NUMA balancing
echo 0 | sudo tee /proc/sys/kernel/numa_balancing
# Or via sysctl
echo "kernel.numa_balancing = 0" | sudo tee -a /etc/sysctl.d/99-numa.conf
IRQ Affinity
On high-traffic servers, spreading network interrupt handling across CPU cores prevents bottlenecks on a single core.
# Install irqbalance
sudo dnf install irqbalance -y
sudo systemctl enable --now irqbalance
# For manual control (high-performance NICs like Intel X710)
# Check current IRQ affinity
cat /proc/interrupts | grep eth0
# Set IRQ affinity to specific CPU mask
echo "ff" | sudo tee /proc/irq/<irq_number>/smp_affinity
Disk I/O Scheduling
RHEL 9 uses mq-deadline or none (for NVMe) by default. For specific workloads:
# Check current scheduler for a disk
cat /sys/block/sda/queue/scheduler
# For databases on SSDs/NVMe — none (bypass scheduler)
echo none | sudo tee /sys/block/nvme0n1/queue/scheduler
# For spinning disks with mixed I/O — mq-deadline
echo mq-deadline | sudo tee /sys/block/sda/queue/scheduler
# Adjust read-ahead (lower for random, higher for sequential)
blockdev --setra 256 /dev/sda
Profiling and Measurement
Don't tune blind — measure first:
# System-wide CPU/IO/memory stats
vmstat 1 10
iostat -xz 1 5
mpstat -P ALL 1 5
# Network stats
ss -s
netstat -s | grep -i retransmit
# Per-process profiling
sudo dnf install perf -y
sudo perf stat -a sleep 5 # system-wide counters
sudo perf top # live perf counter view
# Flame graphs (for CPU profiling)
sudo perf record -ag -- sleep 10
sudo perf report
Quick Reference by Workload
| Workload | tuned Profile | Key Settings |
|---|---|---|
| PostgreSQL/MySQL | throughput-performance | THP=never, swappiness=10, dirty_ratio=15 |
| Redis/Memcached | latency-performance | THP=never, TCP_NODELAY, cpu governor=performance |
| Nginx/HAProxy | network-throughput | tcp_rmem/wmem large, somaxconn=65535 |
| Kubernetes node | throughput-performance | max_map_count=262144, file-max=1000000 |
| Low-latency trading | latency-performance | C-states=1, NUMA pinned, IRQ affinity set |
Tune iteratively: apply one change, measure the impact, then proceed. A profile that helps under load testing may behave differently in production with real access patterns.
Was this article helpful?
Linux Systems Engineer
Everything runs on Linux — I make sure it runs well. From kernel tuning to systemd debugging, I live in the terminal. If your server is misbehaving, I've probably seen that exact dmesg output before.
Related Articles
Linux Kernel Tuning for Network Performance: sysctl Settings That Matter
The sysctl knobs that actually improve Linux network throughput, latency, and connection handling — with benchmarks and safe production values.
RHEL 9 Subscription Manager: Attaching Entitlements, Enabling Repos, and Going Offline
A practical guide to RHEL 9's subscription management — attaching entitlements, enabling the right repositories, creating local mirrors for air-gapped environments, and managing subscriptions at scale.
Migrating from CentOS to Rocky Linux: A Practical Conversion Guide
How to migrate existing CentOS 7/8 servers to Rocky Linux 8 or 9 using the migrate2rocky script — including pre-migration checks, the migration process itself, and post-migration validation steps.
Linux Package Management: apt, dnf, and zypper Compared
Install, update, and manage packages across Ubuntu (apt), RHEL (dnf/yum), and SUSE (zypper) — repositories, GPG keys, version pinning, and automation.
Linux Performance Troubleshooting: CPU, Memory, Disk, and Network
Diagnose Linux performance bottlenecks with top, htop, vmstat, iostat, sar, iotop, and strace — a systematic approach to finding and fixing issues.
Alpine Linux apk Reference: Package Management, Repositories, and System Administration
A complete reference for Alpine Linux's apk package manager — adding packages, managing repositories, pinning versions, building custom packages, and running Alpine as a minimal server OS.
More in Linux
View all →Linux Control Groups (cgroups V2): Limiting CPU And Memory For Production Workloads
If you're running production workloads without cgroup constraints, you're one runaway process away from a bad day. Here's everything you need to configure...
Alpine Linux for Docker: Building Minimal, Secure Container Images
How to use Alpine Linux as a Docker base image effectively — understanding musl libc compatibility, multi-stage builds, layer optimization, and the security trade-offs versus glibc-based distributions.
openSUSE MicroOS and Transactional Updates: Atomic System Updates with Btrfs Snapshots
How openSUSE's transactional-update and Btrfs snapshot integration enables atomic, rollback-safe system updates — and how MicroOS takes this to a fully immutable, container-optimized OS.
openSUSE and SUSE Linux Enterprise: YaST Administration and zypper Package Management
A practical guide to SUSE Linux administration — using YaST for system configuration, zypper for package management, managing repositories, and understanding the differences between openSUSE Leap, Tumbleweed, and SUSE Linux Enterprise.