sysctl Explained: Reading and Changing Kernel Parameters
The kernel exposes hundreds of tunable parameters as files under /proc/sys. sysctl is a thin wrapper that lets you refer to them by dotted name instead of path, and it is how you turn on IP forwarding, adjust swap behaviour, or raise a connection backlog without recompiling anything.
The basics
sysctl -a # every parameter, thousands of them
sysctl vm.swappiness # read one
sudo sysctl -w vm.swappiness=10 # set one, until reboot
sysctl net.ipv4 | head # everything under a prefix
The dotted name maps directly onto a path:
cat /proc/sys/vm/swappiness
echo 10 | sudo tee /proc/sys/vm/swappiness
Both do the same thing. sysctl is just less typing.
Making changes persist
Anything set with -w is gone at reboot. For persistence, use a drop-in file:
sudo tee /etc/sysctl.d/99-custom.conf <<'CONF'
vm.swappiness = 10
net.ipv4.ip_forward = 1
CONF
sudo sysctl --system # reload everything under /etc/sysctl.d/
Drop-in files are preferable to editing /etc/sysctl.conf, because they are easier to manage, easy to remove, and less likely to conflict with what a package ships. Files are read in lexical order, which is why the convention is to prefix with a number.
Verify it took effect rather than assuming:
sysctl vm.swappiness
Settings that are actually worth setting
IP forwarding, if the machine routes between interfaces. This is required for a VPN gateway, a router, or a container host doing its own NAT.
net.ipv4.ip_forward = 1
net.ipv6.conf.all.forwarding = 1
Forgetting this is the classic reason a WireGuard tunnel establishes but no traffic passes.
Swappiness, on a machine with plenty of RAM where you would rather keep process memory resident:
vm.swappiness = 10
The common misunderstanding is that this is a percentage threshold. It is not: it is a relative preference between reclaiming file cache and swapping anonymous pages. Setting it to 0 does not disable swap, it just makes the kernel very reluctant, and on modern kernels that can make out-of-memory situations worse rather than better. Our swap explainer covers the details.
File descriptor limits, on a server handling many connections:
fs.file-max = 2097152
This is the system-wide ceiling. Per-process limits are separate and set through ulimit or systemd’s LimitNOFILE, and raising only one of the two is a common source of confusion.
Connection backlog, on a busy server:
net.core.somaxconn = 4096
net.ipv4.tcp_max_syn_backlog = 4096
somaxconn caps the queue of accepted-but-not-yet-handled connections. The default is low enough that a busy web server can drop connections under load bursts.
Inotify watches, if a file watcher fails with “too many open files”:
fs.inotify.max_user_watches = 524288
This affects editors, IDEs, and development servers watching large trees, and hitting the default limit is a common and confusing failure.
Security-relevant parameters
Most distributions already set sensible values, but these are worth knowing:
# Do not act as a router unless you mean to
net.ipv4.conf.all.forwarding = 0
# Ignore source-routed packets
net.ipv4.conf.all.accept_source_route = 0
# Ignore ICMP redirects
net.ipv4.conf.all.accept_redirects = 0
# Log packets with impossible source addresses
net.ipv4.conf.all.log_martians = 1
# Restrict kernel pointer exposure in /proc
kernel.kptr_restrict = 2
# Restrict unprivileged access to the kernel ring buffer
kernel.dmesg_restrict = 1
That last one is why dmesg asks for root on many systems.
Where tuning guides go wrong
A large amount of published sysctl advice is copied from posts written for a specific workload on a kernel from a decade ago. Applying it wholesale is how people end up with a machine that performs worse and a configuration nobody can explain.
Two rules save most of the trouble. Change one thing at a time and measure, because a batch of ten changes tells you nothing about which mattered. And write down why, because a bare list of values in /etc/sysctl.d/ with no comments is unmaintainable within a year.
# Raised 2026-08 after nginx dropped connections
# during the morning traffic peak. See ticket 4412.
net.core.somaxconn = 4096
Inspecting what is set and by whom
# Everything currently applied
sysctl -a
# Which files contribute
ls /etc/sysctl.d/ /usr/lib/sysctl.d/ /run/sysctl.d/ 2>/dev/null
# Differences between a parameter and its default require checking docs
# The kernel documents them under Documentation/admin-guide/sysctl/
Note that /usr/lib/sysctl.d/ holds vendor defaults and /etc/sysctl.d/ overrides them. If a setting is not taking effect, a higher-numbered file elsewhere is probably setting it afterwards.
Frequently Asked Questions
What is sysctl?
An interface to kernel parameters exposed under /proc/sys. Each parameter is a file you can read and, for the writable ones, change while the system is running. sysctl is a convenience wrapper that uses dotted names instead of paths.
How do I make a sysctl change survive a reboot?
Put it in a file under /etc/sysctl.d/ ending in .conf, then apply it with sysctl --system. Editing /etc/sysctl.conf directly still works but a drop-in file is tidier and less likely to be overwritten by a package update.
What does vm.swappiness actually control?
How readily the kernel swaps anonymous memory out in favour of keeping file cache. It is a preference, not a threshold, and it does not mean the system waits until memory is that percentage full. Lower values favour keeping process memory resident.
Do I need to enable IP forwarding for a VPN?
Yes, if the machine routes traffic between interfaces, which is exactly what a VPN gateway does. Set net.ipv4.ip_forward to 1. Forgetting this is the most common reason a WireGuard tunnel connects but no traffic passes through it.
Is kernel tuning worth doing on a normal desktop?
Rarely. The defaults are sensible for general use and most published tuning guides were written for specific workloads on older kernels. Change a parameter when you have identified a specific problem, not speculatively.
How do I undo a sysctl change?
Runtime changes disappear on reboot, so rebooting is the simplest undo. For persistent changes, remove or comment the line in /etc/sysctl.d/ and either reboot or set the original value manually with sysctl -w.