Right, so you’ve been playing with sysctl on the live kernel, making your system do tricks on the fly. That’s all well and good until you reboot and all your brilliant, finely-tuned parameters vanish into the ether. Poof. Gone. Like you never even cared.

That’s where persistent configuration files come in. They’re your way of telling the system, “Look, these aren’t just suggestions for this boot cycle. I want these settings every time we do this.” The main character in this story is /etc/sysctl.conf, an old warhorse that gets the job done but is starting to show its age. The more modern, organized approach is using drop-in files in /etc/sysctl.d/. You should use the latter for pretty much everything new, but you need to understand both because you’ll inevitably encounter systems that still rely on the old way.

The Old Guard: /etc/sysctl.conf

This file is the original. It’s simple: at boot, the system initialization scripts (usually in your init system, like systemd) run sysctl -p /etc/sysctl.conf to load all the parameters within it. It’s just a text file where you put one parameter per line, using the same dot-notation you’d use with the sysctl command itself, but without the -w.

# /etc/sysctl.conf - Configuration file for setting system parameters
# Enable IP forwarding (makes this a router, basically)
net.ipv4.ip_forward = 1

# Increase the number of incoming connections we can queue
net.core.somaxconn = 1024

# Lessen the fear of SYN flood attacks
net.ipv4.tcp_syncookies = 1

You can apply this file immediately without a reboot by running:

sudo sysctl -p /etc/sysctl.conf

The problem? It’s a single, monolithic file. If you manage systems with configuration management tools like Ansible, Puppet, or Salt, or if you install packages that need their own specific kernel tweaks, everyone is fighting over this one file. It’s a recipe for merge conflicts and messy, hard-to-debug configurations.

The New School: /etc/sysctl.d/

This is the smarter, more modular approach. The idea is that instead of one big file, you have a directory (/etc/sysctl.d/) full of smaller, focused configuration files. The boot process now typically runs sysctl -p /etc/sysctl.d/*.conf, loading them in lexicographical order.

This is vastly superior for several reasons:

  1. Modularity: You can have a file for network tuning (10-network.conf), one for virtual machine performance (50-vm.conf), and another for a specific application you installed (80-my-weird-app.conf).
  2. Package Management: When you install a package (e.g., Docker, a VPN tool, a database), it can drop its own .conf file into /etc/sysctl.d/ without having to parse and modify a shared file. When you remove the package, its config file is removed too. Clean.
  3. Ordering: You control the load order with filename prefixes. Settings in a file named 90-override.conf will load after and potentially override settings from 10-basic.conf.

Here’s how you’d create and apply a custom tuning file:

# Create a new file with your preferred editor, e.g.
sudo nano /etc/sysctl.d/90-custom-tweaks.conf

# Add your parameters. Let's say you want to stop ICMP redirects from cluttering your logs.
net.ipv4.conf.all.accept_redirects = 0
net.ipv6.conf.all.accept_redirects = 0

# Load *just* this new file to test it immediately
sudo sysctl -p /etc/sysctl.d/90-custom-tweaks.conf

The Order of Operations and Precedence

This is critical: files are processed in alphabetical order, and the last value loaded for a given parameter wins. The standard boot process on a modern systemd-powered machine is usually:

  1. /etc/sysctl.d/*.conf
  2. /run/sysctl.d/*.conf (for runtime settings, usually generated by services)
  3. /usr/lib/sysctl.d/*.conf (for default settings provided by installed packages; do NOT edit these)
  4. Finally, for backward compatibility, it runs sysctl -p /etc/sysctl.conf

This means your custom files in /etc/sysctl.d/ will load before the old /etc/sysctl.conf. So, if you have net.ipv4.ip_forward=0 in /etc/sysctl.d/10-network.conf but net.ipv4.ip_forward=1 in /etc/sysctl.conf, the value from sysctl.conf will win because it’s loaded last. This is a common “why isn’t my setting working!?” head-scratcher. The best practice is to pick one method and stick with it. Purge sysctl.conf and put everything in /etc/sysctl.d/ for consistency.

Best Practices and Pitfalls

  • Test Before You Make It Permanent: Always use sysctl -w to test a parameter live first. If it breaks your SSH connection or makes the network vanish, just reboot. Once you know it works, then add it to a config file.
  • Syntax Matters: It’s parameter = value, not parameter=value (though the latter often works, the former is the standard). Comments start with #. Missing the dot in net.ipv4 or misspelling somaxconn will cause the line to be silently ignored. Check your logs (journalctl -xe) after boot for any sysctl errors.
  • Some Parameters Are Read-Only: You can’t change them at runtime, period. They often require a kernel boot parameter instead. If sysctl -w fails on a live system, putting it in a config file won’t magically fix it; it’ll just throw an error during boot.
  • Naming Your Files: Use a numbered prefix (e.g., 10-, 50-, 90-) to clearly define your intended load order. This makes your intent obvious to anyone else (or future you) who has to untangle the system later.

The goal here is to make your changes stick without creating a configuration nightmare. Use /etc/sysctl.d/, name your files clearly, and for the love of all that is holy, clean up the old sysctl.conf if you’re not using it. It’s just sitting there, judging you.