Handbook sections

Hardening the kernel & network with sysctl

Last reviewed

The kernel's runtime tunables decide how the host routes packets and exposes its own memory. Tighten them so the network stack and the kernel itself stop helping an attacker, and make the change persist a reboot.

The threat: a kernel tuned for connectivity, not safety

A Linux kernel ships with runtime tunables (the sysctl keys under /proc/sys) whose defaults favour connectivity and debuggability over security, so the host actively helps an attacker. With IP forwarding on, a compromised server becomes a router that pivots traffic into networks it should never bridge. Accepting ICMP redirects lets any host on the segment rewrite your routing table and man-in-the-middle your traffic. Source-routed packets let a remote sender dictate the return path and bypass filtering. Without reverse-path filtering, spoofed source addresses sail straight in. Locally, an unrandomized address space (ASLR off) makes memory-corruption exploits reliable; leaked kernel pointers in dmesg//proc hand attackers the addresses to defeat KASLR; and an open ptrace scope lets one process read another's memory, harvesting credentials and tokens.

Why harden sysctl

These are one-line settings with outsized impact: each weak default is a primitive an attacker chains into a pivot, a route hijack, a spoofed flood or a reliable exploit. On a typical server (not a router) hardening them costs almost nothing operationally yet removes whole exploitation classes before any service-level control matters. It is the cheapest defense-in-depth layer you own, and the set below maps to CIS and ANSSI BP-028: R8 (memory and ASLR), R9 (kernel), R11 (Yama/ptrace), R12 (IPv4), R13 (IPv6), R14 (filesystem) and R23 (kexec).

Runtime vs persistent: the distinction that decides the grade

sysctl has two faces, and the difference is exactly the qualified-verdict axis:

Action Command Persistence
Read the live value sysctl -n net.ipv4.ip_forward n/a
Set at runtime sysctl -w net.ipv4.ip_forward=0 lost at reboot
Make persistent a file in /etc/sysctl.d/ then sysctl --system survives reboot

For hardening you always write a file in /etc/sysctl.d/; -w is for testing only. Precedence: at the same filename the directory wins (/etc > /run > /usr/lib); across different names the highest lexical name wins, so a 99- prefix is read last and overrides the distribution defaults; sysctl --system reads /etc/sysctl.conf last of all. A value can be live but not persistent (set with -w, lost on reboot) or written but not applied (file edited, sysctl --system never run): only re-reading /proc/sys proves the running state.

What Pavois audits: the effective /proc/sys value

Pavois reads the effective, running value, not what a file claims. Each control uses InSpec's kernel_parameter resource, which reads the live /proc/sys tree, so it sees the kernel's resolved state after every /etc/sysctl.conf, /etc/sysctl.d/*.conf drop-in, kernel command-line argument and runtime sysctl -w has been applied, so a file edited but never applied (sysctl --system not run) is never read as compliant. (SSG's sysctl checks also read /proc/sys, so both catch runtime drift here; the edge is the consistent evidence model, not sysctl itself.) The domain carries dozens of checks: forwarding off, reverse-path filtering on, ICMP redirects ignored and not sent, source routing refused, martian logging, broadcast-echo and bogus-error ignoring, SYN cookies, ASLR = 2, kptr_restrict = 2, dmesg_restrict, and yama.ptrace_scope >= 1, each mapped to CIS, ANSSI BP-028 and more so one audit reports against whichever standard you select.

The hardening parameters

Parameter Hardened value Closes
net.ipv4.ip_forward 0 host used as a router or pivot
net.ipv4.conf.all.accept_redirects 0 ICMP-redirect route hijack / MITM
net.ipv4.conf.all.send_redirects 0 leaking routes to the segment
net.ipv4.conf.all.accept_source_route 0 attacker-dictated return path
net.ipv4.conf.all.rp_filter 1 source-address spoofing
net.ipv4.conf.all.log_martians 1 silent impossible-address traffic
net.ipv4.tcp_syncookies 1 SYN-flood denial of service
net.ipv6.conf.all.accept_ra 0 rogue IPv6 router advertisements
kernel.randomize_va_space 2 reliable memory-corruption exploits
kernel.kptr_restrict 2 KASLR defeat via leaked pointers
kernel.dmesg_restrict 1 kernel-log information leak
kernel.yama.ptrace_scope 1 one process reading another's memory
kernel.unprivileged_bpf_disabled 1 unprivileged eBPF kernel surface
kernel.kexec_load_disabled 1 hot-loading a replacement kernel
fs.protected_symlinks 1 symlink TOCTOU attacks
fs.suid_dumpable 0 core dumps of setuid programs

How do you verify and operate sysctl?

Capture a baseline first (defaults vary by distribution), then read the live values back with sysctl net.ipv4.conf.all.rp_filter kernel.kptr_restrict. For concrete proof, kptr_restrict = 2 zeroes the symbol addresses in head -2 /proc/kallsyms, even for root. Apply a hardening file with sysctl --system. Mind the container limit: kernel.* and fs.* are read-only in an unprivileged container (permission denied); only net.* and a namespaced IPC subset are writable, so full kernel hardening happens on the host or a VM, which containers then inherit.

Exceptions and pitfalls

  • ip_forward = 0 breaks routing. Routers, NAT gateways, Kubernetes nodes and Docker hosts need ip_forward = 1. Only harden it on a plain application server.
  • rp_filter = 1 (strict) breaks asymmetric routing and multi-homed hosts (use 2, loose). The kernel applies max(conf.all, conf.<iface>), so all = 0 does not disable filtering if an interface is higher, and default only affects interfaces created later. CIS and ANSSI want 1; systemd/Debian often keep 2 on purpose.
  • accept_ra = 0 assumes statically configured IPv6; keep it on where prefixes arrive via Router Advertisement.
  • Disabling IPv6 can stop sshd or Postfix bound to ::1 (set AddressFamily inet / inet_protocols = ipv4).
  • Yama must be loaded for kernel.yama.ptrace_scope to exist (cat /sys/kernel/security/lsm).
  • NetworkManager / systemd-networkd set per-interface values after systemd-sysctl: fix a per-interface setting in the network manager, not by stacking sysctl files.
  • Test on a VM or via an out-of-band console: a bad network setting applied remotely can lock you out.

FAQ

Why does Pavois read /proc/sys instead of the sysctl files? Because only the live value is what the kernel enforces. A file present but never applied (no sysctl --system) changes nothing, and Pavois audits the effective state.

A value is correct now but the page fails after a reboot. Why? It was set with sysctl -w (runtime only). Persist it in /etc/sysctl.d/ so it survives a reboot. This is the runtime-versus-persistent distinction the qualified verdict captures.

Can I harden the kernel from inside a container? Not for kernel.* and fs.* (read-only); only net.* and namespaced IPC. Harden the host or the VM, and the container inherits it.