from runtime changes to persistent configuration under /etc/sysctl.d
sysctl makes hundreds of kernel parameters changeable at runtime, but a value set only with sysctl -w disappears again on the next reboot. This article shows how changes become permanent through /etc/sysctl.d, which parameters actually matter for a busy web server such as connection backlog, file limits and swappiness, and why blindly copying someone else's tuning guide often does more harm than good.
Table of Contents
- 1. What sysctl Actually Controls
- 2. Runtime Changes Versus Persistent Configuration
- 3. Connection Backlog: the Queue for New Connections
- 4. File Limits: fs.file-max and Its Relation to ulimit
- 5. vm.swappiness: Memory Behavior Under Load
- 6. Structuring /etc/sysctl.d Correctly
- 7. Validating and Observing Changes Live
- 8. The Risk of Blindly Copied Tuning Guides
- 9. sysctl Parameters Head to Head
- 10. Summary
- 11. FAQ
1. What sysctl Actually Controls
sysctl is the interface through which hundreds of kernel parameters can be read and changed at runtime, without recompiling the kernel or rebooting the system. Technically sysctl is just a tool sitting on top of the real interface: the virtual filesystem under /proc/sys. Every parameter there is a file whose content can be read or overwritten, and sysctl is simply a convenient command line tool for exactly those files.
Parameters are organized into namespaces such as net, vm, fs, and kernel, which map directly onto directory paths under /proc/sys. So net.core.somaxconn corresponds exactly to the file /proc/sys/net/core/somaxconn, with the dots in the sysctl name turning into slashes in the path. This is not an academic footnote, it helps with debugging: when a parameter does not behave via sysctl as expected, it is worth looking directly at the corresponding proc file to see what the kernel actually uses.
2. Runtime Changes Versus Persistent Configuration
The command sysctl -w net.core.somaxconn=4096 changes the value immediately in the running kernel, but this change exists purely in memory. After a reboot, a kernel crash, or even a scheduled reboot for a kernel update, the parameter is back at its default value. This exact distinction between transient and persistent configuration is the most common source of the phenomenon where a tuning change suddenly appears to stop working after a maintenance window.
For the change to persist, the parameter needs to live in a file under /etc/sysctl.d/. At boot time, systemd-sysctl reads all matching files and applies them in a defined order, so the setting survives every reboot. Anyone using sysctl -w for a live production test should always transfer the value into a configuration file once the setting has proven itself, otherwise the work disappears without a trace at the next reboot.
#!/usr/bin/env bash
# Runtime-only change: lost on the next reboot
sudo sysctl -w net.core.somaxconn=4096
# Verify the current runtime value
sysctl net.core.somaxconn
# Same value, read directly from the proc filesystem
cat /proc/sys/net/core/somaxconn
3. Connection Backlog: the Queue for New Connections
For a busy web server behind nginx or php-fpm, backlog parameters are often the first ones to hit a wall. net.core.somaxconn limits how many fully established connections may sit in the queue waiting on accept() by the application. The historical default value of 128 dates from a time of much lower connection rates and is frequently too low on a modern production system with thousands of concurrent requests, causing dropped connections exactly during traffic spikes.
In addition, net.ipv4.tcp_max_syn_backlog limits the number of half-open connections still waiting on the final handshake step before SYN cookies kick in as a protection mechanism. Importantly, the somaxconn value alone is not enough, the application itself must request a backlog at least as large when calling listen(), otherwise the smaller of the two values wins. In nginx the backlog directive inside the listen block controls exactly this application-side value, which needs to match the kernel parameter.
# /etc/sysctl.d/99-webserver.conf
# Applied at boot and on demand via sysctl --system
# Increase the accept queue for a busy web server behind nginx/php-fpm
net.core.somaxconn = 4096
# Allow more half-open connections to queue before SYN cookies kick in
net.ipv4.tcp_max_syn_backlog = 8192
# Raise the system-wide file descriptor ceiling
fs.file-max = 2097152
# Prefer page cache over swapping anonymous pages on a web/app server
vm.swappiness = 10
# Reuse TIME_WAIT sockets faster under high connection churn
net.ipv4.tcp_fin_timeout = 15
4. File Limits: fs.file-max and Its Relation to ulimit
fs.file-max defines the system-wide ceiling on open file descriptors across all processes, not per process. Every open file, every socket, and every named pipe counts against this limit. On a web server with many concurrent connections, log files, and open database sockets, the kernel default, which is often calculated based on available RAM, can turn out surprisingly tight, especially on systems with limited RAM but a lot of short-lived connections.
The important distinction from ulimit is this: fs.file-max sets the system-wide ceiling, while ulimit -n, or the LimitNOFILE directive in a systemd unit, sets the limit per process. A high fs.file-max value is useless if the php-fpm or nginx process is still blocked by a low LimitNOFILE. Both layers need to line up, otherwise the application fails with Too many open files even though the system limit still has plenty of headroom.
#!/usr/bin/env bash
set -euo pipefail
# Load every *.conf file under /etc/sysctl.d/ in lexical order
sudo sysctl --system
# Show exactly which files were read and in what order
sudo sysctl --system 2>&1 | grep -E '^\* '
# Confirm a specific parameter matches the intended value
expected=4096
actual=$(sysctl -n net.core.somaxconn)
[[ "$actual" -eq "$expected" ]] && echo "OK: somaxconn=$actual" || echo "MISMATCH: got $actual"
5. vm.swappiness: Memory Behavior Under Load
vm.swappiness is another parameter that frequently gets left at its default of 60 on web servers without a second thought, even though that value is a compromise designed for desktop systems. It controls the relative weighting between reclaiming page cache and swapping out anonymous memory pages when the kernel needs to free up space. For a web server whose performance depends heavily on page cache for assets, templates, and OPcache data, a lower value between 10 and 20 favors keeping that cache over swapping out application memory.
A low value is not a free pass, however: under genuine memory pressure the kernel still swaps, a value of 0 merely means page cache is favored until the very last resort, not that swap is never used. Anyone adjusting vm.swappiness should watch the si and so columns with vmstat 1 in parallel, to see whether the change actually reduces swapping or whether the real bottleneck is simply too little RAM, something no kernel parameter can compensate for.
6. Structuring /etc/sysctl.d Correctly
Instead of collecting every parameter in the monolithic /etc/sysctl.conf, it is worth splitting configuration into several topical files under /etc/sysctl.d/, for example 99-webserver.conf for network and file limits and 99-security.conf for security-related parameters. Files are loaded in lexical order by filename, so a two-digit number at the start of the filename explicitly fixes the load order. If two files collide on the same parameter, the file loaded last wins, which can lead to hard-to-trace overrides when naming is unclear.
Distributions and packages drop their own files under /usr/lib/sysctl.d/, which count as vendor defaults and may be overridden by administrative changes under /etc/sysctl.d/. Custom configuration files belong exclusively in /etc/sysctl.d/, never in the vendor directories, since those get silently overwritten on a package update. Consistent filenames with a prefix, such as 99-mironsoft-webserver.conf, also make it immediately obvious which file came from which deployment.
# ansible: apply and persist sysctl values across a fleet of web servers
- name: Tune kernel parameters for busy web servers
ansible.posix.sysctl:
name: "{{ item.name }}"
value: "{{ item.value }}"
sysctl_file: /etc/sysctl.d/99-webserver.conf
reload: true
loop:
- { name: net.core.somaxconn, value: "4096" }
- { name: net.ipv4.tcp_max_syn_backlog, value: "8192" }
- { name: fs.file-max, value: "2097152" }
- { name: vm.swappiness, value: "10" }
7. Validating and Observing Changes Live
After creating a configuration file, sysctl --system confirms which files were loaded in what order and reports syntax errors immediately instead of silently ignoring them. The older sysctl -p command only loads the classic /etc/sysctl.conf and misses the files under /etc/sysctl.d/, which is why sysctl --system is the more reliable choice on current systems. sysctl -a prints the complete active parameter set, filtered with grep down to the relevant namespace, for example sysctl -a | grep ^net.core.
For production monitoring it is worth regularly comparing the desired target state against the actually active value, since a manual sysctl -w tweak by a colleague or a broken deployment can let state drift unnoticed. A simple monitoring script that periodically checks the critical parameters against their expected values catches such deviations before they turn into a problem during a traffic spike.
{
"check": "kernel_parameter_drift",
"interval_seconds": 300,
"parameters": [
{ "key": "net.core.somaxconn", "expected": "4096" },
{ "key": "fs.file-max", "expected": "2097152" },
{ "key": "vm.swappiness", "expected": "10" }
],
"action": {
"on_drift": "log",
"on_missing_file": "page_oncall"
}
}
8. The Risk of Blindly Copied Tuning Guides
Many tuning guides found online come from a different kernel version, a different workload profile, or simply a different era of hardware, and blindly copying these values can do more harm than good. A classic example is net.ipv4.tcp_syncookies = 0, which some old performance guides recommend to supposedly save overhead. In reality this value disables protection against SYN flood attacks entirely, opening up a real security risk for a minimal, barely measurable speed gain.
Just as risky is copying net.ipv4.ip_forward = 1 from a Docker or router tuning guide onto a plain web server, which accidentally turns the system into an IP router forwarding packets between network interfaces, a security problem on a publicly reachable server. Every parameter in a configuration file should carry a comment explaining exactly why that value was chosen and which specific problem it solves. Without that reasoning, a sysctl file turns into a black box after a few years that nobody dares touch anymore, afraid of breaking something important.
9. sysctl Parameters Head to Head
The table below compares typical misconfigurations against recommended settings for a busy web server. It does not replace an individual analysis of your own workload, but it does point in the direction a deliberate configuration should move, compared to unreflective default values or blindly copied guides.
| Parameter | Wrong Setting | Recommended Setting |
|---|---|---|
| net.core.somaxconn | Default value 128, unchanged | 4096, matching the application's backlog |
| net.ipv4.tcp_max_syn_backlog | Default value 128 to 256 | 8192 for high connection rates |
| fs.file-max | Kernel default sized for small systems | 2097152, aligned with LimitNOFILE |
| vm.swappiness | 60 (desktop default), unchanged | 10 to 20 for web and application servers |
| net.ipv4.tcp_syncookies | Blindly set to 0 from an old guide | Left at 1 (default), SYN flood protection active |
| Configuration location | Edited directly in /etc/sysctl.conf | Dedicated file under /etc/sysctl.d/99-*.conf |
In practice, a short checklist before every tuning change delivers the most safety: understand the purpose of the parameter, document the value, test the change first with sysctl -w, and only make it permanent under /etc/sysctl.d/ once confirmed, then aim monitoring at the actually active values rather than at the mere existence of a configuration file.
Mironsoft
Server tuning, kernel configuration, and performance monitoring
Kernel parameters that match your real workload instead of a copied guide?
We analyze existing sysctl configurations, identify risky or outdated values, and set backlog, file limits, and memory behavior to match the actual server load, permanently documented under /etc/sysctl.d.
Kernel Audit
Reviewing existing sysctl configuration for risky and outdated values
Workload Tuning
Configuring backlog, file-max, and swappiness to match the real load
Drift Monitoring
Detecting deviations between the target configuration and the active kernel state
10. Summary
sysctl is the central tool for controlling kernel behavior on Linux servers without recompiling anything, but the distinction between a transient runtime change with sysctl -w and a persistent configuration under /etc/sysctl.d/ determines whether a tuning change survives a reboot. For busy web servers, backlog parameters such as net.core.somaxconn and net.ipv4.tcp_max_syn_backlog, the system-wide file ceiling fs.file-max in combination with ulimit, and vm.swappiness for memory behavior under load are the parameters with the largest practical impact.
The biggest risk factor is not a single wrongly set value, it is blindly copying someone else's tuning guide without understanding which problem a parameter actually solves and what side effects it carries, as the tcp_syncookies = 0 example shows. Documenting every parameter, testing changes at runtime first, and only then transferring them permanently into a clearly named file under /etc/sysctl.d/ builds a traceable, maintainable kernel configuration instead of a black box nobody dares to touch anymore.
Tuning Linux Kernel Parameters with sysctl: the Key Takeaways
Runtime vs. Persistent
sysctl -w only lasts until the next reboot. A value only becomes permanent through a file under /etc/sysctl.d/.
Backlog & File Limits
net.core.somaxconn and fs.file-max are frequently set too low for web servers with many concurrent connections.
vm.swappiness
Values between 10 and 20 favor page cache over swapping, genuine memory pressure still swaps regardless.
Copy-Paste Risk
Adopting someone else's tuning guide without understanding each parameter can disable security mechanisms.