📑 Daftar Isi
- Why Default Kernel Settings Fail in Production
- Step 0: Back Up and Establish a Baseline
- Step 1: Tune the Network Stack
- Step 2: Tune the Filesystem
- Step 3: Tune Memory Management
- Step 4: Boot Parameters via GRUB
- Step 5: Set the Right I/O Scheduler
- Step 6: Apply and Verify
- Step 7: Test and Monitor
- Pro Tips and Common Mistakes
- Troubleshooting Table: When Tuning Goes Wrong
- FAQ
AlmaLinux Kernel Tuning for Production: Complete sysctl & Boot Parameter Guide 2026
Skip the fluff. You’ve got a fresh AlmaLinux server and it needs to handle real traffic. The stock kernel ships tuned for a typical desktop — safe, stable, and slower than it needs to be under load. This guide covers exactly what I change on production AlmaLinux boxes, what each knob does, and how to verify the changes actually stuck.
So here’s the deal. Think of stock kernel settings like a rental car’s mirrors — they work for everyone, but they’re not right for you. Before a long drive you adjust the seat, the mirrors, and check the tires. Kernel tuning is the same idea: you’re not rebuilding anything, just adjusting a handful of values to match your workload. Five minutes of work, measurable difference under load.
Why Default Kernel Settings Fail in Production
Default kernel values are a compromise tuned for general use. They work fine on a laptop or a small office server. But production is different: thousands of concurrent TCP connections, database workloads, heavy file I/O, and apps that spawn more threads than the kernel ever expected. When limits get hit, the kernel doesn’t crash — it degrades quietly. Connections get dropped, latency becomes inconsistent, sockets sit in TIME_WAIT for a minute, and the server feels slow even though CPU and RAM look healthy.
That’s the trap. Resource graphs look fine, so everyone blames the app, the network, or the hosting provider. Meanwhile the real problem is a backlog queue that’s too small, or a swap policy that’s too aggressive, or Transparent Huge Pages causing latency spikes on a database. I’ve debugged too many “server feels slow” tickets that came down to a handful of sysctl values.
The impact matters. Dropped connections at the kernel level mean clients retry, API calls fail mid-flight, and background sync jobs break silently. For any business relying on uptime, that’s money and trust leaking out. And because none of it shows up as a loud error, it’s easy to miss. Tuning the kernel closes these gaps before they cost you a client. It’s boring, unglamorous work — and that’s exactly why it’s usually left undone.
Here’s the approach, step by step. Do them in order, verify each one, and you’ll have a server that handles load the way it should.
Step 0: Back Up and Establish a Baseline
Before touching anything, back up the current sysctl config and record the values you’re about to change. If something goes sideways, you’ll know exactly what to restore. Think of it as taking photos of a room before you rearrange the furniture.
cp /etc/sysctl.conf /etc/sysctl.conf.bak-$(date +%F)
sysctl -a | grep -E 'net.core.somaxconn|net.ipv4.tcp_tw_reuse|vm.swappiness|fs.file-max'
cat /sys/kernel/mm/transparent_hugepage/enabled
lsblk -d -o NAME,ROTA,SCHED
Expected baseline on a stock AlmaLinux 8/9 box: net.core.somaxconn is 4096, vm.swappiness is 30, and THP is set to always. fs.file-max varies with RAM size. Write these down. Also, make sure you have out-of-band console access (provider panel, VNC, KVM) before you start — if you misconfigure a network parameter over SSH, that console is your way back in.
Step 1: Tune the Network Stack
This is where most servers see the biggest win, especially web servers and APIs. Three things cause the classic symptoms: connection backlog too small, sockets lingering too long, and a port range that runs out. Fix all three and a lot of weird behavior disappears.
Here’s the sysctl block I drop into every production server. I keep it in a separate file so it’s easy to review and remove.
cat > /etc/sysctl.d/60-tuning.conf <<'EOF'
# --- Network Stack ---
net.core.somaxconn = 65535
net.core.netdev_max_backlog = 65536
net.ipv4.tcp_max_syn_backlog = 65536
net.ipv4.tcp_fin_timeout = 30
net.ipv4.tcp_tw_reuse = 1
net.ipv4.ip_local_port_range = 1024 65535
net.ipv4.tcp_slow_start_after_idle = 0
net.ipv4.tcp_fastopen = 3
net.ipv4.tcp_sack = 1
net.ipv4.tcp_window_scaling = 1
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216
EOF

What each one does, briefly:
- net.core.somaxconn — the listen() backlog queue. Nginx, Apache, and Node.js all use it. Default 4096; production gets 65535. Too small and the kernel starts refusing connections you can't even see in your app logs.
- net.core.netdev_max_backlog — packets queued at the NIC before the CPU processes them. When this overflows, packets are silently dropped. That's invisible latency.
- net.ipv4.tcp_max_syn_backlog — pending SYN queue before the handshake completes. Critical during SYN floods or heavy bursts.
- net.ipv4.tcp_fin_timeout — how long a socket stays in FIN_WAIT_2. Default 60 seconds; 30 is plenty.
- net.ipv4.tcp_tw_reuse — allows reusing TIME_WAIT sockets for new outbound connections. Big one for servers that make lots of outbound connections (backend to API, cron to services).
- net.ipv4.ip_local_port_range — ephemeral ports for outbound connections. Default starts at 32768; expanding to 1024 prevents "can't assign requested address" under connection churn.
- net.ipv4.tcp_slow_start_after_idle — set to 0 so the congestion window isn't reset after idle periods. Helps short, frequent connections.
- tcp_rmem / tcp_wmem / rmem_max / wmem_max — TCP buffers. Larger buffers help long-haul connections between datacenters.
Quick checklist for the network section: if your server is a public web server with mostly inbound connections, you can skip tcp_tw_reuse. If it's a backend, app server, or anything with outbound traffic, keep it. Check ss -s before and after to see the difference in TIME_WAIT counts.
Step 2: Tune the Filesystem
Two settings here, both easy to forget, both painful when hit. File descriptor limits and inotify watches.
cat >> /etc/sysctl.d/60-tuning.conf <<'EOF'
# --- Filesystem ---
fs.file-max = 2097152
fs.inotify.max_user_watches = 524288
fs.inotify.max_user_instances = 1024
EOF
fs.file-max is the global open-file limit. When you hit it, apps can't open files and you get errors that look nothing like a resource problem. Set it explicitly so you don't discover the limit at 2 AM.
fs.inotify.max_user_watches is the limit on file and folder watches. If you've ever seen "ENOSPC: System limit for number of file watchers reached" — from Laravel, Node.js, Docker, or any file watcher — this is the fix. Set it, move on.
Step 3: Tune Memory Management
This section is where opinions get loud. Stick to the values below — they're battle-tested across the servers we manage — and you'll be fine.
cat >> /etc/sysctl.d/60-tuning.conf <<'EOF'
# --- Memory ---
vm.swappiness = 10
vm.dirty_ratio = 20
vm.dirty_background_ratio = 10
vm.vfs_cache_pressure = 50
vm.max_map_count = 262144
EOF
vm.swappiness — default 30 means the kernel moves pages to swap fairly eagerly. For servers, especially ones with databases or Redis, drop it to 10 so the kernel keeps data in RAM while there's room. Don't set 0. Zero can trigger unnecessary OOM kills when the system genuinely runs low on memory. 10 is the sweet spot.
vm.dirty_ratio / vm.dirty_background_ratio — controls how much dirty (unsynced) data can accumulate before the kernel flushes it. background_ratio 10 starts background flushing early; dirty_ratio 20 is the hard cap. These prevent disk write bursts and keep write-heavy workloads smooth.
vm.vfs_cache_pressure — default 100. Lowering to 50 tells the kernel to keep dentry and inode caches around longer. Noticeable on hosts with lots of small file access — think cPanel or shared hosting.
vm.max_map_count — Elasticsearch and memory-map-heavy apps burn through the default 65530 quickly. The error "max virtual memory areas vm.max_map_count [65530] is too low" is a direct giveaway. 262144 is the standard fix.
One more: if the box is a dedicated database, some people add vm.overcommit_memory = 2. It makes the kernel reject overcommit beyond the swap plus RAM limit, which Redis likes. But it can also make memory-hungry apps fail to start. Start with the safe values above; experiment only if you know your workload.
Step 4: Boot Parameters via GRUB
sysctl changes apply at runtime. Some settings only make sense at boot time. Two of them matter most: Transparent Huge Pages and CPU mitigations.
Transparent Huge Pages (THP) — Linux merges 4KB pages into 2MB huge pages automatically. Good for general workloads, bad for databases. THP's background compaction causes unpredictable latency spikes that show up as khugepaged errors in dmesg. Disable it on database servers; leave it alone on plain web servers.
CPU mitigations — Meltdown and Spectre mitigations are on by default and cost a few percent of CPU. Tempting to disable with mitigations=off, but that's a security trade-off. Only consider it on isolated systems with no sensitive data. For anything with customer data or compliance requirements, keep the default.
To edit boot parameters, modify /etc/default/grub and rebuild the config:
# Disable THP on a database server
sudo sed -i 's/^GRUB_CMDLINE_LINUX=.*/GRUB_CMDLINE_LINUX="rhgb quiet transparent_hugepage=never"/' /etc/default/grub
sudo grub2-mkconfig -o /boot/grub2/grub.cfg
⚠️ SAFETY WARNING: Back Up Before You Proceed
Before rebuilding grub.cfg: 1) Back up /etc/default/grub, 2) Save the original config so you can roll back, 3) Confirm out-of-band console access works. A bad boot parameter can leave the server unable to boot, and if SSH is your only way in, you're in trouble.
cp /etc/default/grub /etc/default/grub.bak-$(date +%F)
Then run grub2-mkconfig and reboot in a maintenance window. Don't reboot during peak hours — you'll find out the hard way why that's a bad idea.
Step 5: Set the Right I/O Scheduler
The I/O scheduler decides how read and write requests queue up per disk. Modern kernels pick a default automatically, but it's often not ideal for specific hardware.
NVMe drives want none — the controller handles scheduling itself, no software layer needed. SATA SSDs do well with mq-deadline. HDDs also work with mq-deadline. Check and set yours:
# Check current scheduler
cat /sys/block/nvme0n1/queue/scheduler
# Set to none for NVMe (runtime only)
echo none > /sys/block/nvme0n1/queue/scheduler
# Persist via udev rule
cat > /etc/udev/rules.d/60-iosched.rules <<'EOF'
ACTION=="add|change", KERNEL=="nvme*", ATTR{queue/scheduler}="none"
EOF
Small change, measurable difference on I/O-heavy workloads. Most people skip this step — don't be most people.
Step 6: Apply and Verify
All config is written to /etc/sysctl.d/60-tuning.conf. Time to apply it. Use sysctl --system on AlmaLinux 8/9 — it reads every file under /etc/sysctl.d/ and /usr/lib/sysctl.d/ in order.
# Apply all settings
sysctl --system
# Verify
sysctl net.core.somaxconn
sysctl vm.swappiness
sysctl fs.file-max
Expected output:
net.core.somaxconn = 65535
vm.swappiness = 10
fs.file-max = 2097152
If a value doesn't match, another file in /etc/sysctl.d/ is overriding yours. Files are read in lexicographic order — later files win. That's why I prefix with 60-, so my settings get read last and stick.
GRUB and udev settings only take effect after a reboot. If you want THP disabled without rebooting:
echo never > /sys/kernel/mm/transparent_hugepage/enabled
cat /sys/kernel/mm/transparent_hugepage/enabled
Remember: that's runtime-only. It reverts on reboot unless the GRUB change is in place.
Step 7: Test and Monitor
Tuning without testing is guesswork. Take a baseline before changes, then re-run the same tests after. My standard kit: sysbench for CPU and memory, iperf3 for network, ab or wrk for HTTP.
# CPU benchmark
sysbench cpu --threads=8 --time=30 run
# Memory benchmark
sysbench memory --threads=4 --time=30 run
# Network (from a client box to the server)
iperf3 -c 203.0.113.10 -t 30
# HTTP load test
ab -n 50000 -c 500 http://203.0.113.10/
Benchmarks are nice, but real load tells the truth. Watch whether TIME_WAIT piles up, whether backlog overflows disappear, whether swap gets touched. If you don't have monitoring yet, set up Netdata on Linux — it'll show you these numbers in real time.
# Check socket states
ss -s
# Check for backlog overflows
dmesg | grep -i "backlog"
Pro Tips and Common Mistakes
After tuning hundreds of production boxes, a few patterns keep repeating. Here's what bites people, so you can skip the pain.
First, copying parameters from an article without context. Values that fit a database server don't necessarily fit a web server, and vice versa. These knobs are workload-dependent. Read what each one does, then decide if it applies to you.
Second, no baseline. If you never measured performance before and after, you can't tell whether a change helped or hurt. I record initial benchmark results, then compare again a week later. Decisions come from data, not feelings.
Third, changing everything at once in production. If you can, apply one group of parameters first (say, just the network block), watch it for a few days, then move to the next. When something breaks, isolation is trivial.
Fourth, and this is the most common one: forgetting sysctl read order. Files in /etc/sysctl.d/ are read alphabetically, so if two files set the same value, the later file wins. That's why I prefix my file with 60- — it gets read late and sticks. Drop yours in a 10- prefixed file and it can be silently overridden.
Last: rebooting during peak hours after a GRUB edit. Don't. Maintenance windows exist for a reason. If you misconfigure THP or mitigations and the box won't boot during business hours, that's a ticket you don't want. Schedule the reboot, verify out-of-band access, then go.
Troubleshooting Table: When Tuning Goes Wrong
| Symptom | Likely Cause | Where to Check | Fix |
|---|---|---|---|
| Connection refused or resets under load | somaxconn or tcp_max_syn_backlog too small | ss -ltn, dmesg | Raise net.core.somaxconn, watch backlog with ss |
| Thousands of TIME_WAIT sockets | Short-lived connections plus slow FIN | ss -s | Enable tcp_tw_reuse, lower tcp_fin_timeout |
| "too many open files" | File descriptor exhaustion | ulimit -n, /proc/sys/fs/file-max | Raise fs.file-max and process ulimits |
| "inotify watch limit reached" | Watchers exhausted | /proc/sys/fs/inotify/max_user_watches | Raise fs.inotify.max_user_watches |
| Swap used while RAM is free | swappiness too high for workload | free -h, vmstat | Tune vm.swappiness |
| DB latency spikes, khugepaged errors | Transparent Huge Pages enabled | dmesg, THP sysfs | Set transparent_hugepage=never in GRUB |
| Server won't boot after GRUB edit | Bad boot parameter | Out-of-band console | Boot old kernel or rescue, restore grub backup |
If you apply sysctl changes and networking suddenly breaks, don't panic. Reboot (or use the console) to restore defaults, or remove the sysctl.d file you created. This is exactly why step 0 exists — you have the original values written down.
FAQ
Q: Is kernel tuning safe on every production server?
Most of it is, with context. The network and filesystem parameters above are common values that rarely cause problems. vm.swappiness and THP depend on workload — databases want THP off and low swappiness; plain web servers can leave both. Always back up and apply changes incrementally rather than all at once.
Q: Do sysctl changes survive a reboot?
Yes, if written to /etc/sysctl.conf or a file in /etc/sysctl.d/. Runtime commands via sysctl or writing to /proc/sys only last until reboot. GRUB boot options and udev rules take effect at boot. Know which of your changes are persistent and which are runtime-only.
Q: What's the ideal vm.swappiness value?
10 is the widely used, safe default for production servers. Database servers can go 0-5, but 0 makes the OOM killer more aggressive if RAM genuinely runs out. Web servers work fine at 10-30. Don't blindly set 0 everywhere — it can backfire.
Q: Is disabling CPU mitigations safe?
It's a trade-off. Mitigations protect against side-channel attacks like Spectre and Meltdown and cost a few percent of performance. Disabling them (mitigations=off) speeds things up but reopens known vulnerabilities. Not recommended for anything holding sensitive data or under compliance rules. If raw performance is the absolute priority on an isolated box, the responsibility is yours.
Q: How do I roll back if tuning causes problems?
For sysctl: remove or edit your file in /etc/sysctl.d/, then run sysctl --system to restore earlier values. For GRUB: restore your /etc/default/grub backup, rebuild grub.cfg, and reboot. If the server won't boot, use the out-of-band console to boot an older kernel or rescue mode. Backups and baselines are non-negotiable.
That's the whole process — seven steps, about fifteen minutes, and a production server that stops fighting your workload. Do the steps in order, keep the baseline, and verify each change. Before you close out, make sure you've: 1) backed up original configs, 2) verified values with sysctl --system, 3) confirmed the box still boots cleanly. All good? Case closed. Related reading: Linux server monitoring with Netdata, SSH hardening for VPS, and high load average troubleshooting. If you're running a web server, Nginx optimization for high traffic and MySQL/MariaDB optimization are worth a read too.