sysctl is not a configuration file format. It is a live view of the kernel’s parameter table, reachable by dotted name and mirrored under /proc/sys, so reading and writing a value is literally reading and writing a pseudo-file. That is why sysctl -w takes effect immediately and why nothing set that way survives a reboot.

The tension is all in the second half: persistence. The runtime interface is honest and immediate, while the file meant to make a setting stick is subject to filename precedence, an implementation split over /etc/sysctl.conf, and a failure mode where a line quietly does nothing.

A key is a path into a live table

Every dotted key maps to one pseudo-file, net.ipv4.ip_forward to /proc/sys/net/ipv4/ip_forward, and sysctl(8) notes that procfs is required. sysctl -a enumerates the table, skipping deprecated and verboten parameters, and one exclusion is a warning in itself: stat_refresh returns no value but flushes per-CPU VM statistics, so reading everything is not always free.

Because it is a live table rather than stored configuration, the key set is stable and the values are not: on the macOS host used for the fixture, sysctl -a returned 1917 keys across 14 namespaces, and two consecutive reads produced an identical key set while 47 lines changed value. Reads and writes share one handler: an unknown key gives ENOENT, a read-only key refuses the write, and an unparseable value gives EINVAL. No daemon holds state.

Runtime first, persistence second

sysctl -w key=value applies to the running kernel and writes nothing anywhere, which makes it the right tool for testing. Persisting means a file the init system reads, and there the two implementations disagree: procps sysctl --system reads /etc/sysctl.d, /run/sysctl.d, /usr/local/lib/sysctl.d, /usr/lib/sysctl.d and /lib/sysctl.d, then /etc/sysctl.conf last, while systemd-sysctl reads the drop-in directories and, as sysctl.conf(5) states, does not use /etc/sysctl.conf at all.

Precedence is by filename, not directory: all configuration files are sorted lexicographically regardless of where they live, a same-named file in an earlier directory masks the later one, and for a given key the lexicographically latest filename wins. Hence the convention of a two-digit prefix and a dash, 10-40 for vendor files and 60-90 for local ones, so the local file sorts last regardless of which directory the reader walked first.

The failure is a line that quietly did nothing

sysctl.d(5) splits errors into two classes, and the split is the whole story. A permission error, or an attempt to write a variable that is not present on the local system, is logged at debug level and does not fail the service; other errors are louder and do cause a failure. So a line for a module-gated key, such as net.bridge.bridge-nf-call-iptables before br_netfilter is loaded, is dropped quietly and sysctl --system still exits zero. The page’s remedy is to load the module first, via a udev rule or modules-load.d.

The second quiet failure is ordering. The value in effect is whatever the last writer set, so a container runtime or a unit’s own ExecStartPre writing the same key after your drop-in wins without touching your file, and a reload that reports success is not evidence that your value is the one in effect. The - prefix, or -e, suppresses even the loud errors, so a typo becomes invisible too.

When it breaks

The classic failure is a config that reads correctly and does nothing. The file is on disk, sysctl --system exits zero, and the key still holds its default, because it did not exist when the loader ran or because something wrote it afterwards. Diagnose by reading the key back rather than re-reading the file: sysctl <key> shows the effective value, and comparing it against what the file claims is the whole test.

The second failure is a container that believes it tuned the host. net.* is per network namespace, so two containers can hold different values, but vm.* and most kernel.* keys are global; from a container they either change the whole machine or fail with EROFS when /proc/sys is read-only. A sysctl -w in an entrypoint is therefore not portable, and one that silently does nothing is worse than one that fails.

Check it quickly

sysctl -a | wc -l
sysctl net.ipv4.ip_forward net.core.somaxconn
sysctl -n net.ipv4.ip_forward
sysctl --system; echo "exit=$?"
grep -r . /etc/sysctl.d/ 2>/dev/null | head

The first line proves the table is enumerable, and the next two show the difference that matters when scripting: plain sysctl prints key: value, while -n prints the bare value. sysctl --system should exit zero, but that exit code proves very little, because a missing key is debug-level and non-fatal, so follow it with sysctl <key> for every key you claim to manage. The grep shows which files exist; remember that a same-named file in /run or /usr/lib may be masked, and that /etc/sysctl.conf is read by procps sysctl but not systemd-sysctl.

Sources

  • sysctl(8), the runtime interface, the –system file order, and the deprecated and verboten parameters, checked 2026-09-16.
  • sysctl.conf(5), the line format, the “-” prefix, and the note that systemd-sysctl does not read /etc/sysctl.conf, checked 2026-09-16.
  • sysctl.d(5), drop-in precedence and the error classes that decide whether a failed line is even reported, checked 2026-09-16.