open atlas
↑ Back to track
Linux, the operating system LIN · 12 · 01

Kernel parameters with sysctl

The kernel exposes thousands of tunables under /proc/sys. sysctl -w sets them at runtime (lost on reboot); /etc/sysctl.d/*.conf makes them permanent. Key knobs: vm.swappiness, net.ipv4.ip_forward (required for routing and containers), fs.file-max.

LIN Middle ◷ 20 min
Level
FoundationsJuniorMiddleSenior

A developer pushes a service that opens thousands of persistent connections. The box starts refusing new connections at 02:00. The fix is a one-liner — sysctl -w net.core.somaxconn=4096 — but finding it requires knowing that the kernel exposes its own configurable internals through a virtual filesystem at /proc/sys, and that every directory in that tree maps to a dot-separated parameter name. The same mechanism controls whether your host forwards packets (essential for containers and VPNs), how aggressively the kernel swaps memory to disk, and the maximum number of open file descriptors. You do not need to recompile to change any of this — you just write to the right file or run sysctl. What you do need to know is that a runtime change evaporates on the next reboot unless you also persist it.

Goal

After this lesson you can list and read kernel parameters with sysctl -a, set them at runtime with sysctl -w, persist them across reboots via /etc/sysctl.d/*.conf and sysctl --system, and explain the purpose of vm.swappiness, net.ipv4.ip_forward, and fs.file-max.

1

The /proc/sys tree: how the kernel exposes tunables. Every kernel parameter visible to sysctl lives as a file under /proc/sys. The directory hierarchy maps directly to the dot-separated name: net.ipv4.ip_forward lives at /proc/sys/net/ipv4/ip_forward. You can read and write files directly with cat and echo, but sysctl is the correct tool — it validates values and formats error messages.

# List all parameters and their current values (several thousand lines):
sysctl -a

# Filter to a subsystem:
sysctl -a | grep vm.

# Read one parameter:
sysctl vm.swappiness
# vm.swappiness = 60

# Read directly from /proc/sys (same value, less convenient):
cat /proc/sys/vm/swappiness
# 60

# Read the raw file for a network parameter:
cat /proc/sys/net/ipv4/ip_forward
# 0

Most parameters are integers. Some accept comma-separated lists (e.g. kernel.sched_domain.cpu0.domain0.flags). A value of 0 is typically “disabled”, 1 is “enabled” — but always read the man page for anything you change in production.

2

Runtime changes with sysctl -w. A runtime change takes effect immediately and survives until the next reboot. This is exactly what you want during incident response — you can tune, observe, and if the tuning made things worse, reboot to reset to the previous state.

# Enable IP forwarding (required for routing and containers):
sudo sysctl -w net.ipv4.ip_forward=1
# net.ipv4.ip_forward = 1

# Verify:
sysctl net.ipv4.ip_forward
# net.ipv4.ip_forward = 1

# Lower swappiness (kernel will prefer to keep more in RAM):
sudo sysctl -w vm.swappiness=10
# vm.swappiness = 10

# Raise the system-wide open file limit:
sudo sysctl -w fs.file-max=2097152
# fs.file-max = 2097152

# GOTCHA: this setting is lost on reboot
# After the next reboot:
sysctl vm.swappiness
# vm.swappiness = 60   ← back to default

The gotcha is the most common operational mistake. An engineer tuning a performance issue during an incident applies sysctl -w, the problem is resolved, the change is documented in a ticket — and then vanishes the next maintenance reboot. Persistence requires a separate step.

3

Persistent settings via /etc/sysctl.d. The sysctl configuration directories are read at boot by the kernel and at any time by sysctl --system. Drop a .conf file into /etc/sysctl.d/ — do not edit /etc/sysctl.conf directly (package upgrades can reset it).

# Create a project-specific tuning file:
sudo tee /etc/sysctl.d/99-myapp.conf << 'EOF'
# Allow the host to forward packets (needed for Docker, WireGuard, etc.)
net.ipv4.ip_forward = 1

# Low swappiness for a latency-sensitive service
vm.swappiness = 10

# System-wide open file limit for a high-connection service
fs.file-max = 2097152
EOF

# Apply ALL /etc/sysctl.d/*.conf files without rebooting:
sudo sysctl --system
# Applying /etc/sysctl.d/10-console-messages.conf ...
# Applying /etc/sysctl.d/99-myapp.conf ...
# net.ipv4.ip_forward = 1
# vm.swappiness = 10
# fs.file-max = 2097152

# Verify one specific parameter is now persistent:
cat /etc/sysctl.d/99-myapp.conf | grep ip_forward
# net.ipv4.ip_forward = 1

Files are processed in lexicographic order. 99- prefix ensures your file wins over distribution defaults (which use prefixes like 10- or 60-). A parameter set in a later file always overrides one set in an earlier file.

4

Three parameters every fullstack engineer should know.

# 1. vm.swappiness (default: 60)
# Controls how aggressively the kernel swaps pages to disk.
# 0 = swap only when the system is completely out of RAM
# 60 = default, suitable for desktops
# 10 = latency-sensitive servers (database hosts, app servers)
# 100 = swap as aggressively as possible (rarely useful)
sysctl vm.swappiness

# 2. net.ipv4.ip_forward (default: 0)
# When set to 0, the kernel DROPS packets addressed to another host.
# This breaks: Docker bridge networking, WireGuard VPN, any NAT setup.
# Without ip_forward=1, a container on a different subnet cannot reach the internet.
# Docker sets this on startup — but it reverts to 0 on reboot without sysctl.d.
sysctl net.ipv4.ip_forward

# 3. fs.file-max (default: varies, often ~100000)
# The system-wide limit on open file descriptors (all processes combined).
# Each network socket, open file, and pipe costs one file descriptor.
# A busy web server or database easily hits this at tens of thousands of connections.
# Note: per-process limit is controlled separately by ulimit / /etc/security/limits.conf
sysctl fs.file-max

# See all current values that differ from kernel defaults (useful for auditing):
sysctl -a 2>/dev/null | grep -v "^#"

The ip_forward parameter is the one most likely to bite you in a container or VPN context: containers start, but have no internet connectivity, because the host was rebooted without the persistent sysctl, Docker’s own startup re-enables it at runtime, but your custom routing rules reference an interface that wasn’t up yet. Always persist ip_forward in sysctl.d.

5

Loading and verifying the full configuration. After any change to /etc/sysctl.d/, use sysctl --system to apply without rebooting. After a reboot, the same files are applied automatically by systemd-sysctl.service.

# See which files sysctl --system will process and in what order:
ls -la /etc/sysctl.d/ /usr/lib/sysctl.d/ /run/sysctl.d/ 2>/dev/null

# Apply all files and see what changed:
sudo sysctl --system

# Confirm a specific value was applied from your file:
sysctl net.ipv4.ip_forward
# net.ipv4.ip_forward = 1

# Check the systemd unit that applies sysctl on boot:
systemctl status systemd-sysctl.service
# Loaded: loaded (/lib/systemd/system/systemd-sysctl.service; static)
# Active: inactive (dead)
# (inactive is correct — it runs once at boot and exits)

# If you want to apply only your new file (not all files):
sudo sysctl -p /etc/sysctl.d/99-myapp.conf
# net.ipv4.ip_forward = 1
# vm.swappiness = 10
# fs.file-max = 2097152
Worked example

Scenario: a container host keeps losing ip_forward after reboots.

Every time the host reboots, Docker containers lose internet connectivity for the first few minutes, and the on-call engineer manually runs sysctl -w net.ipv4.ip_forward=1 before alerting fires. The problem has recurred six times.

# Step 1: Confirm the problem
sysctl net.ipv4.ip_forward
# net.ipv4.ip_forward = 0   ← Docker hasn't started yet or it lost the race

# Step 2: Check if there is already a sysctl.d file for this:
grep -r ip_forward /etc/sysctl.d/ /etc/sysctl.conf 2>/dev/null
# (no output — nothing is persisting it)

# Step 3: Docker sets ip_forward at its own startup, but it competes with
# other early-boot services. Check Docker's own sysctl setting:
cat /proc/sys/net/ipv4/ip_forward
# 0  ← Docker hasn't started yet in this boot stage

# Step 4: Create a persistent override:
sudo tee /etc/sysctl.d/99-docker-forwarding.conf << 'EOF'
# Required for Docker networking and container internet access
net.ipv4.ip_forward = 1
# Also enable for IPv6 if containers use IPv6:
# net.ipv6.conf.all.forwarding = 1
EOF

# Step 5: Apply now (don't wait for reboot):
sudo sysctl --system | grep ip_forward
# net.ipv4.ip_forward = 1

# Step 6: Verify it survives reboot by checking the unit that applies it:
systemctl is-enabled systemd-sysctl.service
# static   ← always runs, cannot be disabled

# Step 7: Reboot and verify (or trust the systemd-sysctl unit to do it):
sysctl net.ipv4.ip_forward
# net.ipv4.ip_forward = 1

Root cause: Docker enables ip_forward at its own startup but the sysctl persists no longer than the Docker daemon’s lifetime. A missing /etc/sysctl.d/ entry means every cold boot starts with forwarding disabled. Fix: one conf file, one sysctl --system invocation, never touch it again.

Common mistake

Editing /etc/sysctl.conf directly. The file /etc/sysctl.conf is the legacy location and is processed first. However, package upgrades (kernel, procps) can overwrite or reset it. The correct pattern is to drop a file in /etc/sysctl.d/ with a high numeric prefix (e.g. 99-) so your settings are applied last and win. Think of it the same way as /etc/cron.d/ vs. editing the root crontab — the drop-in directory pattern survives upgrades and is auditable.

Why this works

macOS does not have /proc/sys. macOS uses a BSD-derived kernel with its own sysctl interface: sysctl -a works, but the parameter namespaces are completely different (kern.maxfiles instead of fs.file-max, net.inet.ip.forwarding instead of net.ipv4.ip_forward). There is no /proc/sys virtual filesystem. If you are developing locally on macOS and testing Linux behavior, the parameters you tune on your Mac do not translate — Docker Desktop on macOS runs a full Linux VM, and the sysctl values you need to set are inside that VM, not on the Mac host.

Check yourself
Quiz

You run `sudo sysctl -w net.ipv4.ip_forward=1` on a production host and confirm it works. The next day after a routine reboot, container networking is broken again. What is the correct fix?

Recap

The kernel exposes thousands of runtime tunables through the /proc/sys virtual filesystem, each accessible by its dot-separated name via sysctl. Read with sysctl -a (all) or sysctl <name> (one). Set at runtime with sysctl -w name=value — takes effect immediately, lost on reboot. Persist by placing a .conf file in /etc/sysctl.d/ (use a 99- prefix to win over defaults), then run sudo sysctl --system to apply without rebooting; systemd-sysctl.service applies the same files automatically at every boot. Three parameters every fullstack engineer needs: vm.swappiness (how eagerly the kernel swaps; lower for latency-sensitive services), net.ipv4.ip_forward (must be 1 for containers and VPNs; forgotten persistence is the most common container networking bug), and fs.file-max (system-wide open file descriptor limit for high-connection services).

Practice

Start at the top. Tasks go easiest → hardest: recall a fact, apply it to a case, then a senior-level stretch. Open one, attempt it, then reveal.

recallapplystretch0 of 4 done

Something unclear?

Ask a question about this lesson. Questions are anonymous and go straight to the author to make the lesson better.

shortcuts expand
search
K
prev piece
k
next piece
j
cycle tier
t
this menu
?
sources2
expand
  1. 01
  2. 02

Trademarks belong to their respective owners. Editorial reference only.