SYSCTL.D(5)

๐Ÿ’– NAME ๐Ÿ’–

sysctl.d โ€” okay so this is section 5 of the manual, bestie, and it's literally the whole vibe of "how do I configure kernel parameters at boot" โœจ


โœจ SYNOPSIS โœจ

    /etc/sysctl.d/*.conf
    /run/sysctl.d/*.conf
    /usr/local/lib/sysctl.d/*.conf
    /usr/lib/sysctl.d/*.conf

key.name.under.proc.sys = some value
key/name/under/proc/sys = some value
key/middle.part.with.dots/foo = 123
key.middle/part/with/dots.foo = 123
-key.that.will.not.fail = value
key.pattern.*.with.glob = whatever
-key.pattern.excluded.with.glob
key.pattern.overridden.with.glob = custom

๐ŸŒธ DESCRIPTION ๐ŸŒธ

so here's the tea: at boot, systemd-sysctl.service(8) reads the configuration files from all those directories listed above and uses them to configure sysctl(8) kernel parameters. that's the entire plot, no subplot, no twist ๐Ÿซถ


๐ŸŽ€ CONFIGURATION FORMAT ๐ŸŽ€

okay so a config file is just a list of variable assignments, one per line, separated by newlines. that's it. empty lines get ignored, and so do lines whose first non-whitespace character is # or ; โ€” those are your comments, queen ๐Ÿ’…

now here's the part everyone gets confused about, so pay attention bestie: you can use either / or . as a separator inside a sysctl variable name. not both-at-once-arbitrarily, there's a rule:

  • if the first separator is a slash, then all the remaining slashes and dots are left intact ๐Ÿฆ‹
  • if the first separator is a dot, then dots and slashes get interchanged ๐Ÿ”„

so like, kernel.domainname=foo and kernel/domainname=foo are equivalent, and both of them cause foo to be written to /proc/sys/kernel/domainname. periodt.

and either net.ipv4.conf.enp3s0/200.forwarding or net/ipv4/conf/enp3s0.200/forwarding can be used to refer to /proc/sys/net/ipv4/conf/enp3s0.200/forwarding. iconic, honestly.

you can also use a glob glob(7) pattern to write the same value to all matching keys ๐ŸŒˆ. but listen โ€” keys for which an explicit pattern exists get excluded from any glob matching. and on top of that, you can explicitly exclude a key from being set by any matching glob patterns by writing the key name prefixed with a - character and not followed by =. check the SYNOPSIS up there, it's all laid out ๐Ÿ’–

also! any access permission errors, and any attempts to write variables that aren't present on the local system, get logged at debug level and do not cause the service to fail. no drama. but other types of errors when setting variables get logged with higher priority and cause the service to return failure at the end โ€” after it's processed the other variables, so at least it finishes the fit first ๐Ÿ˜ญ

as an exception: if a variable assignment is prefixed with a single - character, then failure to set that variable for any reason gets logged at debug level and will not cause the service to fail. bestie's got grace ๐Ÿซถ

the settings configured with sysctl.d files get applied early on boot. and the network interface-specific options get applied individually for each network interface as it shows up in the system. (more specifically: net.ipv4.conf.*, net.ipv6.conf.*, net.ipv4.neigh.* and net.ipv6.neigh.*.) ๐Ÿ’…

now, the sad part ๐Ÿ˜ญ โ€” many sysctl parameters only become available when certain kernel modules are loaded. and modules are usually loaded on demand, like when certain hardware gets plugged in or a network gets brought up. which means systemd-sysctl.service(8) โ€” which runs during early boot โ€” will not configure those parameters if they become available after it has run. so unfair.

to set such parameters, the recommendation is: add a udev(7) rule to set those parameters when they become available. alternatively, a slightly simpler and less efficient option is to add the module to modules-load.d(5), which causes it to be loaded statically before sysctl settings are applied (see the example below, it's a good one ๐ŸŒธ).


๐Ÿ‘‘ CONFIGURATION DIRECTORIES AND PRECEDENCE ๐Ÿ‘‘

config files get read from directories in /etc/, /run/, /usr/local/lib/, and /usr/lib/ โ€” in order of precedence, exactly like the SYNOPSIS section listed them above. files must have the .conf extension, no exceptions, that's the dress code.

the override hierarchy, in full:

  • files in /etc/ override files with the same name in /run/, /usr/local/lib/, and /usr/lib/.
  • files in /run/ override files with the same name under /usr/.

all configuration files get sorted by their filename in lexicographic order, regardless of which of the directories they live in. and if multiple files specify the same option, the entry in the file with the lexicographically latest name takes precedence. so basically: a config in a certain file can either be replaced completely (by dropping a file with the same name in a directory with higher priority), or you can just change individual settings (by specifying additional settings in a file with a different name that's ordered later). your choice, bestie, both are valid ๐Ÿ’…

packages should install their configuration files in /usr/lib/ (that's distribution packages) or /usr/local/lib/ (that's local installs) [1]. and files in /etc/ are reserved for the local administrator, who may use this logic to override the configuration files installed by vendor packages. that's your turf, own it ๐Ÿ‘‘

it is recommended to prefix all filenames with a two-digit number and a dash to simplify the ordering. and it's recommended to use the range 10-40 for configuration files in /usr/ and the range 60-90 for configuration files in /etc/ and /run/ โ€” that way local and transient configuration files will always take priority over the configuration files shipped by the OS vendor. yes this is a whole system. yes it works. no we're not arguing about it ๐ŸŽ€

if the administrator wants to disable a configuration file supplied by the vendor, the recommended way is to place a symlink to /dev/null in the configuration directory in /etc/, with the same filename as the vendor configuration file. galaxy brain move tbh ๐ŸŒŒ. but โ€” if the vendor configuration file is included in the initrd image, the image has to be regenerated. don't forget that part, it will absolutely bite you ๐Ÿ™ˆ


๐ŸŒˆ EXAMPLES ๐ŸŒˆ

๐Ÿ“ Example 1. Set kernel YP domain name

/etc/sysctl.d/domain-name.conf:

kernel.domainname=example.com

๐Ÿ“ Example 2. Apply settings available only when a certain module is loaded (method one)

/etc/udev/rules.d/99-bridge.rules:

ACTION=="add", SUBSYSTEM=="module", KERNEL=="br_netfilter", \
      RUN+="/usr/lib/systemd/systemd-sysctl --prefix=/net/bridge"

/etc/sysctl.d/bridge.conf:

net.bridge.bridge-nf-call-ip6tables = 0
net.bridge.bridge-nf-call-iptables = 0
net.bridge.bridge-nf-call-arptables = 0

this method applies settings when the module is loaded. please note that, unless the br_netfilter module is loaded, bridged packets will not be filtered by Netfilter (starting with kernel 3.18), so simply not loading the module is sufficient to avoid filtering. lowkey elegant ๐Ÿฆ‹

๐Ÿ“ Example 3. Apply settings available only when a certain module is loaded (method two)

/etc/modules-load.d/bridge.conf:

br_netfilter

/etc/sysctl.d/bridge.conf:

net.bridge.bridge-nf-call-ip6tables = 0
net.bridge.bridge-nf-call-iptables = 0
net.bridge.bridge-nf-call-arptables = 0

this method forces the module to be always loaded. please note that, unless the br_netfilter module is loaded, bridged packets will not be filtered by Netfilter (starting with kernel 3.18), so simply not loading the module is sufficient to avoid filtering. same disclaimer, different strategy, that's the duality of girl ๐Ÿ’…

๐Ÿ“ Example 4. Set network routing properties for all interfaces

/etc/sysctl.d/20-rp_filter.conf:

net.ipv4.conf.default.rp_filter = 2
net.ipv4.conf.*.rp_filter = 2
-net.ipv4.conf.all.rp_filter
net.ipv4.conf.hub0.rp_filter = 1

the rp_filter key will be set to "2" for all interfaces, except "hub0".

here's why, bestie, follow along: we set net.ipv4.conf.default.rp_filter first, so any interfaces which are added later will get this value (this also covers any interfaces detected while we're running). the glob matches any interfaces which were detected earlier. the glob will also match net.ipv4.conf.all.rp_filter, which we do not want to set at all, so it is explicitly excluded. and "hub0" is excluded from the glob because it has an explicit setting. see? every line has a job. no line is unemployed. ๐Ÿ‘‘


๐Ÿ’œ SEE ALSO ๐Ÿ’œ

systemd(1), systemd-sysctl.service(8), systemd-delta(1), sysctl(8), sysctl.conf(5), modprobe(8)


๐Ÿงจ NOTES ๐Ÿงจ

  1. ๐Ÿ’ฃ๐Ÿ’ฅ๐Ÿงจ๐Ÿ’ฅ๐Ÿ’ฅ๐Ÿ’ฃ please note that those configuration files must be available at all times. if /usr/local/ is a separate partition, it may not be available during early boot, and must not be used for configuration. like, seriously, do not.

okay that's the whole page, bestie, you ate it ๐Ÿ’… this was sysctl.d from systemd 262.1, page SYSCTL.D(5), and yes I will be here whenever you need to reread it ๐ŸŽ€โœจ๐Ÿฆ‹๐Ÿ’–

๐Ÿ“ฆ where this page comes from
  • arch rolling: systemd 262.1-1 ยท LGPL-2.1-or-later AND CC0-1.0 AND GPL-2.0-or-later AND MIT-0
  • ๐Ÿ’– girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms