The Linux kernel exposes hundreds of tunable parameters through the /proc/sys filesystem. Here's the thing most people don't realize: almost all the defaults prioritize compatibility over security. Attackers absolutely know this. With thousands of kernel CVEs reported each year and the vast majority of Linux compromises exploiting misconfigurations rather than malware, hardening these parameters is honestly one of the highest-impact security moves you can make.
I've spent years deploying sysctl hardening configs across production fleets, and this guide is what I wish I had when I started. We'll cover every critical sysctl security parameter organized by attack surface, provide a ready-to-deploy configuration file, map each setting to CIS Benchmarks and NIST 800-53 controls, and throw in automation scripts so you can enforce this stuff consistently.
So, let's dive in.
How sysctl Works and Where Settings Live
The sysctl command reads and writes kernel parameters exposed as virtual files under /proc/sys. A parameter like net.ipv4.ip_forward maps directly to the file /proc/sys/net/ipv4/ip_forward. Simple enough.
Configuration File Hierarchy
Settings can be persisted across reboots using configuration files. The kernel processes them in this order, with later files overriding earlier ones:
/etc/sysctl.conf— the legacy default file/etc/sysctl.d/*.conf— drop-in directory (this is what you should use)/usr/lib/sysctl.d/*.conf— distribution defaults
Use a high-numbered file like /etc/sysctl.d/99-security-hardening.conf to make sure your security settings take precedence over distribution defaults. I can't tell you how many times I've seen hardening configs get silently overridden by a distro update because someone used a low-numbered filename.
Boot-Time Parameters
Since Linux 5.8, you can set sysctl values via the kernel command line using sysctl.parameter=value syntax. This applies settings before any userspace service runs — which is critical for parameters that need to be active from the earliest boot stages:
# In /etc/default/grub, append to GRUB_CMDLINE_LINUX:
GRUB_CMDLINE_LINUX="sysctl.kernel.unprivileged_bpf_disabled=1 sysctl.kernel.kptr_restrict=2"
# Then update GRUB:
sudo update-grub # Debian/Ubuntu
sudo grub2-mkconfig -o /boot/grub2/grub.cfg # RHEL/Rocky
Kernel Self-Protection Parameters
These are your first line of defense. They restrict information leakage and disable dangerous kernel features that attackers love to exploit for privilege escalation.
Restrict Kernel Log Access
# Restrict dmesg to root only
# Prevents unprivileged users from reading kernel log messages
# that may contain memory addresses and other sensitive information
# CIS Benchmark: 1.5.2 | NIST: SI-11
kernel.dmesg_restrict = 1
Hide Kernel Pointers
# Hide kernel symbol addresses from all users
# Value 2: addresses hidden from all users regardless of capabilities
# Prevents attackers from using /proc/kallsyms to locate exploit targets
# CIS Benchmark: 1.5.3 | NIST: SI-16
kernel.kptr_restrict = 2
This one is a must. Without it, any user on the system can essentially get a roadmap to your kernel's memory layout.
Restrict eBPF Access
# Disable unprivileged eBPF
# eBPF programs run in kernel context and present a significant attack surface
# Requires CAP_BPF capability (or root) to load BPF programs
# CIS Benchmark: N/A | NIST: CM-7
kernel.unprivileged_bpf_disabled = 1
# Harden BPF JIT compiler
# Value 2: full hardening with constant blinding to prevent JIT spraying attacks
# CIS Benchmark: N/A | NIST: SI-16
net.core.bpf_jit_harden = 2
Disable Kexec and SysRq
# Disable kexec — prevents loading a new kernel at runtime
# An attacker with root could use kexec to load a malicious kernel
# CIS Benchmark: N/A | NIST: CM-7
kernel.kexec_load_disabled = 1
# Disable SysRq key — prevents dangerous keyboard-triggered kernel commands
# SysRq can reboot, kill processes, and remount filesystems
# Value 0: completely disabled
# CIS Benchmark: N/A | NIST: CM-7
kernel.sysrq = 0
Restrict Process Tracing
# Restrict ptrace to parent processes only (Yama LSM)
# Value 1: only parent processes can ptrace children
# Value 2: only processes with CAP_SYS_PTRACE can use ptrace
# Value 3: ptrace completely disabled
# CIS Benchmark: 1.5.4 | NIST: AC-3
kernel.yama.ptrace_scope = 2
I typically go with value 2 for servers. Value 3 (completely disabled) can break debuggers and some monitoring tools, so unless you're running a locked-down appliance, 2 is the sweet spot.
Restrict Unprivileged User Namespaces
# Disable unprivileged user namespaces
# User namespaces expand the kernel attack surface significantly
# NOTE: This may break some container runtimes and browsers (Chromium sandboxing)
# Test thoroughly before enabling on workstations
# CIS Benchmark: N/A | NIST: CM-7
kernel.unprivileged_userns_clone = 0
Fair warning on this one — it will break Chromium-based browsers and rootless containers. More on that in the troubleshooting section below.
Restrict userfaultfd
# Restrict userfaultfd to privileged users
# userfaultfd is frequently abused in use-after-free exploits
# Value 1: requires CAP_SYS_PTRACE
# CIS Benchmark: N/A | NIST: CM-7
vm.unprivileged_userfaultfd = 0
Restrict Performance Events
# Restrict perf_event_open to root
# Value 3: disallow all access to perf events by unprivileged users
# Perf events can leak kernel information through side channels
# CIS Benchmark: N/A | NIST: SI-16
kernel.perf_event_paranoid = 3
Memory and Filesystem Protection
These settings harden memory allocation, protect against symlink-based attacks, and enforce address space randomization. If the kernel self-protection section was about locking the front door, this is about securing the windows too.
Address Space Layout Randomization (ASLR)
# Enable full ASLR
# Value 2: randomize stack, VDSO, mmap, and heap
# Makes exploitation significantly harder by randomizing memory layout
# CIS Benchmark: 1.5.3 | NIST: SI-16
kernel.randomize_va_space = 2
# Increase mmap ASLR entropy (x86_64 values)
# More entropy bits = more randomization = harder to brute-force
vm.mmap_rnd_bits = 32
vm.mmap_rnd_compat_bits = 16
ASLR should already be on by default in any modern distro, but it's worth explicitly setting it. You don't want a package update or config change quietly turning it off.
Symlink and Hardlink Protection
# Protect against symlink following in world-writable sticky directories
# Prevents TOCTOU race conditions in /tmp and similar directories
# CIS Benchmark: 1.7.1 | NIST: AC-6
fs.protected_symlinks = 1
# Prevent hardlink creation to files without read/write access
# Blocks hardlink-based privilege escalation attacks
# CIS Benchmark: 1.7.1 | NIST: AC-6
fs.protected_hardlinks = 1
# Protect FIFOs and regular files in world-writable directories
# Value 2: applies to all users, not just group-writable dirs
fs.protected_fifos = 2
fs.protected_regular = 2
Core Dump Restrictions
# Disable core dumps for SUID programs
# Core dumps from privileged programs can leak sensitive data
# CIS Benchmark: 1.5.1 | NIST: SI-11
fs.suid_dumpable = 0
Network Hardening Parameters
Network-level sysctl parameters form a critical defense layer against spoofing, flooding, and man-in-the-middle attacks. This section tends to have the most parameters, and honestly, it's where I see the most misconfigurations in the wild. Apply these to both IPv4 and IPv6 where applicable.
IP Forwarding
# Disable IP forwarding (unless this system is a router or gateway)
# Prevents the system from being used as a hop point in routing attacks
# CIS Benchmark: 3.1.1 | NIST: SC-7
net.ipv4.ip_forward = 0
net.ipv6.conf.all.forwarding = 0
Source Routing and ICMP Redirects
# Disable source routing — sender-specified routes are almost never legitimate
# CIS Benchmark: 3.2.1 | NIST: SC-7
net.ipv4.conf.all.accept_source_route = 0
net.ipv4.conf.default.accept_source_route = 0
net.ipv6.conf.all.accept_source_route = 0
net.ipv6.conf.default.accept_source_route = 0
# Disable ICMP redirect acceptance — prevents route table poisoning
# CIS Benchmark: 3.2.2 | NIST: SC-7
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.default.accept_redirects = 0
net.ipv6.conf.all.accept_redirects = 0
net.ipv6.conf.default.accept_redirects = 0
# Disable secure ICMP redirects
# Even "secure" redirects from known gateways can be spoofed
# CIS Benchmark: 3.2.3 | NIST: SC-7
net.ipv4.conf.all.secure_redirects = 0
net.ipv4.conf.default.secure_redirects = 0
# Do not send ICMP redirects (unless acting as a router)
# CIS Benchmark: 3.1.2 | NIST: SC-7
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.default.send_redirects = 0
SYN Flood Protection
# Enable TCP SYN cookies
# Mitigates SYN flood attacks by not allocating resources until handshake completes
# CIS Benchmark: 3.2.8 | NIST: SC-5
net.ipv4.tcp_syncookies = 1
# Limit SYN backlog and retries for further DoS protection
net.ipv4.tcp_max_syn_backlog = 2048
net.ipv4.tcp_synack_retries = 2
net.ipv4.tcp_syn_retries = 5
IP Spoofing Protection
# Enable strict reverse path filtering
# Drops packets with source addresses that do not match the expected interface
# Value 1: strict mode — recommended for most servers
# CIS Benchmark: 3.2.7 | NIST: SC-7
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1
Logging and Broadcast Protection
# Log martian packets (impossible source addresses)
# Useful for detecting spoofing attempts and misconfigurations
# CIS Benchmark: 3.2.4 | NIST: AU-12
net.ipv4.conf.all.log_martians = 1
net.ipv4.conf.default.log_martians = 1
# Ignore ICMP broadcast echo requests (Smurf attack mitigation)
# CIS Benchmark: 3.2.5 | NIST: SC-5
net.ipv4.icmp_echo_ignore_broadcasts = 1
# Ignore bogus ICMP error responses
# CIS Benchmark: 3.2.6 | NIST: SC-5
net.ipv4.icmp_ignore_bogus_error_responses = 1
TCP SACK and Timestamps
# Disable TCP SACK if not needed
# SACK has been the target of multiple CVEs (e.g., CVE-2019-11477 "SACK Panic")
# WARNING: Disabling SACK may impact performance on lossy networks
# Evaluate your environment before applying
net.ipv4.tcp_sack = 0
# Enable TCP timestamps for PAWS and RTT estimation
# Despite information leakage concerns, timestamps protect against
# wrapped sequence numbers in high-speed networks
net.ipv4.tcp_timestamps = 1
A quick note on SACK: this is one of those "it depends" settings. On a clean, low-latency datacenter network, disabling SACK is fine. But if you're dealing with any kind of packet loss (think WAN links or flaky cloud networking), you might want to leave it enabled and just make sure your kernel is patched.
IPv6-Specific Hardening
# Disable IPv6 router advertisements if not using IPv6 routing
# Prevents rogue RA attacks on the local network
# CIS Benchmark: 3.3 | NIST: SC-7
net.ipv6.conf.all.accept_ra = 0
net.ipv6.conf.default.accept_ra = 0
# If IPv6 is not used at all, disable it completely
# Reduces attack surface by eliminating an entire protocol stack
# net.ipv6.conf.all.disable_ipv6 = 1
# net.ipv6.conf.default.disable_ipv6 = 1
Complete Hardening Configuration File
Here's the consolidated, production-ready config file with all recommended parameters. Save this as /etc/sysctl.d/99-security-hardening.conf and you're good to go:
###############################################
# Linux Kernel Security Hardening via sysctl
# File: /etc/sysctl.d/99-security-hardening.conf
# Compatible with: Kernel 5.15+ (Ubuntu 22.04+, RHEL 9+, Debian 12+)
# Last updated: 2026-03-30
###############################################
#--- Kernel Self-Protection ---
kernel.dmesg_restrict = 1
kernel.kptr_restrict = 2
kernel.unprivileged_bpf_disabled = 1
net.core.bpf_jit_harden = 2
kernel.kexec_load_disabled = 1
kernel.sysrq = 0
kernel.yama.ptrace_scope = 2
kernel.perf_event_paranoid = 3
vm.unprivileged_userfaultfd = 0
#--- Memory and Filesystem ---
kernel.randomize_va_space = 2
vm.mmap_rnd_bits = 32
vm.mmap_rnd_compat_bits = 16
fs.protected_symlinks = 1
fs.protected_hardlinks = 1
fs.protected_fifos = 2
fs.protected_regular = 2
fs.suid_dumpable = 0
#--- Network: General ---
net.ipv4.ip_forward = 0
net.ipv6.conf.all.forwarding = 0
#--- Network: Source Routing & Redirects ---
net.ipv4.conf.all.accept_source_route = 0
net.ipv4.conf.default.accept_source_route = 0
net.ipv6.conf.all.accept_source_route = 0
net.ipv6.conf.default.accept_source_route = 0
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.default.accept_redirects = 0
net.ipv6.conf.all.accept_redirects = 0
net.ipv6.conf.default.accept_redirects = 0
net.ipv4.conf.all.secure_redirects = 0
net.ipv4.conf.default.secure_redirects = 0
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.default.send_redirects = 0
#--- Network: Flood & Spoof Protection ---
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_max_syn_backlog = 2048
net.ipv4.tcp_synack_retries = 2
net.ipv4.tcp_syn_retries = 5
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1
#--- Network: Logging & ICMP ---
net.ipv4.conf.all.log_martians = 1
net.ipv4.conf.default.log_martians = 1
net.ipv4.icmp_echo_ignore_broadcasts = 1
net.ipv4.icmp_ignore_bogus_error_responses = 1
#--- Network: TCP Options ---
net.ipv4.tcp_sack = 0
net.ipv4.tcp_timestamps = 1
#--- Network: IPv6 ---
net.ipv6.conf.all.accept_ra = 0
net.ipv6.conf.default.accept_ra = 0
Applying and Verifying the Configuration
Alright, you've got the config file. Now let's walk through deploying it safely. Don't just yolo it onto production — follow these steps.
Step 1: Backup Current Settings
# Save current sysctl values as a baseline for rollback
sudo sysctl -a > /root/sysctl-backup-$(date +%Y%m%d).txt
Step 2: Deploy the Configuration
# Copy the hardening file
sudo cp 99-security-hardening.conf /etc/sysctl.d/
# Apply the settings
sudo sysctl --system
Step 3: Verify Critical Parameters
Here's a verification script that checks the most important parameters. Save it as verify-sysctl.sh — you'll want to run this after every deployment and ideally on a schedule (more on that later):
#!/bin/bash
# Verification script — save as verify-sysctl.sh
declare -A EXPECTED=(
["kernel.dmesg_restrict"]="1"
["kernel.kptr_restrict"]="2"
["kernel.randomize_va_space"]="2"
["kernel.yama.ptrace_scope"]="2"
["kernel.unprivileged_bpf_disabled"]="1"
["net.core.bpf_jit_harden"]="2"
["net.ipv4.ip_forward"]="0"
["net.ipv4.tcp_syncookies"]="1"
["net.ipv4.conf.all.rp_filter"]="1"
["net.ipv4.conf.all.accept_source_route"]="0"
["net.ipv4.conf.all.accept_redirects"]="0"
["net.ipv4.conf.all.send_redirects"]="0"
["net.ipv4.conf.all.log_martians"]="1"
["net.ipv4.icmp_echo_ignore_broadcasts"]="1"
["fs.protected_symlinks"]="1"
["fs.protected_hardlinks"]="1"
["fs.suid_dumpable"]="0"
)
PASS=0
FAIL=0
for param in "${!EXPECTED[@]}"; do
actual=$(sysctl -n "$param" 2>/dev/null)
expected="${EXPECTED[$param]}"
if [ "$actual" = "$expected" ]; then
echo "[PASS] $param = $actual"
((PASS++))
else
echo "[FAIL] $param = $actual (expected: $expected)"
((FAIL++))
fi
done
echo ""
echo "Results: $PASS passed, $FAIL failed out of ${#EXPECTED[@]} checks"
[ $FAIL -eq 0 ] && echo "All sysctl hardening checks passed." || exit 1
Automation with Ansible
If you're managing more than a handful of servers (and let's be real, who isn't these days?), doing this manually doesn't scale. Ansible makes it straightforward to enforce sysctl hardening across your entire fleet with a single playbook:
# sysctl-hardening.yml
---
- name: Apply sysctl kernel hardening
hosts: all
become: true
vars:
sysctl_params:
# Kernel self-protection
kernel.dmesg_restrict: 1
kernel.kptr_restrict: 2
kernel.unprivileged_bpf_disabled: 1
net.core.bpf_jit_harden: 2
kernel.kexec_load_disabled: 1
kernel.sysrq: 0
kernel.yama.ptrace_scope: 2
kernel.perf_event_paranoid: 3
vm.unprivileged_userfaultfd: 0
# Memory and filesystem
kernel.randomize_va_space: 2
vm.mmap_rnd_bits: 32
vm.mmap_rnd_compat_bits: 16
fs.protected_symlinks: 1
fs.protected_hardlinks: 1
fs.protected_fifos: 2
fs.protected_regular: 2
fs.suid_dumpable: 0
# Network
net.ipv4.ip_forward: 0
net.ipv4.conf.all.accept_source_route: 0
net.ipv4.conf.default.accept_source_route: 0
net.ipv4.conf.all.accept_redirects: 0
net.ipv4.conf.default.accept_redirects: 0
net.ipv4.conf.all.secure_redirects: 0
net.ipv4.conf.default.secure_redirects: 0
net.ipv4.conf.all.send_redirects: 0
net.ipv4.conf.default.send_redirects: 0
net.ipv4.tcp_syncookies: 1
net.ipv4.conf.all.rp_filter: 1
net.ipv4.conf.default.rp_filter: 1
net.ipv4.conf.all.log_martians: 1
net.ipv4.conf.default.log_martians: 1
net.ipv4.icmp_echo_ignore_broadcasts: 1
net.ipv4.icmp_ignore_bogus_error_responses: 1
net.ipv6.conf.all.accept_ra: 0
net.ipv6.conf.default.accept_ra: 0
tasks:
- name: Apply sysctl security parameters
ansible.posix.sysctl:
name: "{{ item.key }}"
value: "{{ item.value }}"
sysctl_file: /etc/sysctl.d/99-security-hardening.conf
state: present
reload: true
loop: "{{ sysctl_params | dict2items }}"
loop_control:
label: "{{ item.key }}"
- name: Verify critical parameters are applied
ansible.builtin.command: "sysctl -n {{ item.key }}"
loop: "{{ sysctl_params | dict2items }}"
loop_control:
label: "{{ item.key }}"
register: sysctl_verify
changed_when: false
failed_when: sysctl_verify.stdout | trim != item.value | string
Run the playbook against your inventory:
ansible-playbook -i inventory.yml sysctl-hardening.yml --check # dry run first
ansible-playbook -i inventory.yml sysctl-hardening.yml # apply
If your organization already uses the dev-sec/ansible-collection-hardening collection, you're in luck — its os_hardening role applies CIS-aligned sysctl settings automatically and covers most of the parameters in this guide.
Compliance Mapping Reference
This is the table your auditors will love. It maps each sysctl parameter category to the relevant controls across CIS Benchmarks, NIST 800-53, PCI DSS, and DISA STIG:
| Parameter Category | CIS Benchmark | NIST 800-53 | PCI DSS | DISA STIG |
|---|---|---|---|---|
| Kernel pointer/log restriction | 1.5.2, 1.5.3 | SI-11, SI-16 | 2.2 | V-230269 |
| ASLR and memory randomization | 1.5.3 | SI-16 | 2.2 | V-230280 |
| Core dump restriction | 1.5.1 | SI-11 | 2.2 | V-230310 |
| ptrace scope restriction | 1.5.4 | AC-3, AC-6 | 7.1 | V-230544 |
| IP forwarding disabled | 3.1.1 | SC-7 | 1.3 | V-230536 |
| Source routing disabled | 3.2.1 | SC-7 | 1.3 | V-230537 |
| ICMP redirects disabled | 3.2.2, 3.2.3 | SC-7 | 1.3 | V-230538 |
| TCP SYN cookies | 3.2.8 | SC-5 | 6.6 | V-230541 |
| Reverse path filtering | 3.2.7 | SC-7 | 1.3 | V-230540 |
| Martian packet logging | 3.2.4 | AU-12 | 10.2 | V-230539 |
| Broadcast ICMP ignored | 3.2.5 | SC-5 | 6.6 | V-230542 |
| Symlink/hardlink protection | 1.7.1 | AC-6 | 7.1 | V-230311 |
Distro-Specific Considerations
Not all sysctl parameters work the same everywhere. Here are the gotchas I've run into across different distros and environments.
Ubuntu / Debian
kernel.unprivileged_userns_cloneis a Debian-specific patch — it's not in the upstream kernel. Ubuntu 24.04+ uses AppArmor to restrict user namespaces instead via theapparmor_restrict_unprivileged_userns=1boot parameter.- The
yamaLSM is enabled by default on Ubuntu but may need explicit loading on minimal Debian installs. - Ubuntu 24.04+ ships with
kernel.apparmor_restrict_unprivileged_unconfined=1by default.
RHEL / Rocky / AlmaLinux
- SELinux provides additional protections that complement sysctl hardening. Keep SELinux in enforcing mode — seriously, don't disable it.
vm.unprivileged_userfaultfddefaults to0on RHEL 9+ but should still be explicitly set for clarity.- RHEL 9 uses kernel 5.14, so some parameters like
vm.mmap_rnd_bits=32require kernel 5.15+. RHEL does backport many fixes though, so check/proc/sysfor actual availability.
Container Hosts (Docker / Kubernetes Nodes)
This is where things get tricky.
- Keep
net.ipv4.ip_forward = 1on Kubernetes nodes — container networking flat-out requires forwarding. - Do not set
kernel.unprivileged_userns_clone = 0on container hosts unless all containers run as root. User namespaces are essential for rootless containers. - Kubernetes also needs
vm.overcommit_memory = 1andvm.panic_on_oom = 0for proper pod scheduling. - All other hardening parameters listed here are safe for container hosts. Apply them.
Monitoring and Drift Detection
Here's something that catches a lot of people off guard: hardening isn't a one-time task. Kernel parameters can be modified at runtime by any process with sufficient privileges, and package updates can quietly reset values. You need continuous monitoring to detect configuration drift.
Systemd Timer for Periodic Verification
# /etc/systemd/system/sysctl-audit.service
[Unit]
Description=Verify sysctl security hardening parameters
[Service]
Type=oneshot
ExecStart=/usr/local/bin/verify-sysctl.sh
StandardOutput=journal
StandardError=journal
# /etc/systemd/system/sysctl-audit.timer
[Unit]
Description=Run sysctl audit every 6 hours
[Timer]
OnBootSec=5min
OnUnitActiveSec=6h
Persistent=true
[Install]
WantedBy=timers.target
# Enable the timer
sudo systemctl enable --now sysctl-audit.timer
# Check results in journal
sudo journalctl -u sysctl-audit.service --no-pager -n 50
Integration with OpenSCAP
For organizations that use OpenSCAP for compliance scanning, the good news is that sysctl parameters are automatically checked as part of CIS and DISA STIG profiles:
# Install OpenSCAP and security guide
sudo dnf install openscap-scanner scap-security-guide # RHEL/Rocky
sudo apt install libopenscap8 ssg-debian # Debian/Ubuntu
# Run a CIS benchmark scan including sysctl checks
sudo oscap xccdf eval \
--profile xccdf_org.ssgproject.content_profile_cis \
--results /tmp/sysctl-scan-results.xml \
--report /tmp/sysctl-scan-report.html \
/usr/share/xml/scap/ssg/content/ssg-rl9-ds.xml
Troubleshooting Common Issues
Kernel hardening can break things. That's just the reality. Here are the issues I see most often and how to deal with them.
Chromium/Electron Apps Crash After Disabling User Namespaces
Chromium uses unprivileged user namespaces for its sandbox. If you set kernel.unprivileged_userns_clone = 0, you can launch Chromium with --no-sandbox — but that defeats the purpose and I wouldn't recommend it. On Ubuntu 24.04+, the better approach is to use AppArmor profiles to selectively restrict namespaces rather than a blanket disable.
Docker Containers Fail to Start
If net.ipv4.ip_forward = 0, container networking simply won't work. On dedicated container hosts, keep forwarding enabled and harden everything else.
Application Performance Degradation with TCP SACK Disabled
Disabling tcp_sack can cause noticeable throughput drops on networks with packet loss. If you see degraded performance after applying this change, re-enable SACK and make sure your kernel is patched against known SACK CVEs instead. It's a reasonable trade-off.
Parameters Not Persisting After Reboot
Check that your configuration file isn't being overridden by a higher-numbered file in /etc/sysctl.d/. Use sudo sysctl --system 2>&1 | grep "Applying" to see the load order, and verify your file is processed last.
Frequently Asked Questions
What is the difference between sysctl.conf and sysctl.d?
/etc/sysctl.conf is the legacy single configuration file. /etc/sysctl.d/ is a drop-in directory where you place multiple .conf files that are loaded in alphabetical order. You should use sysctl.d because it keeps your custom settings separate from distribution defaults, making upgrades cleaner and auditing easier. Files in sysctl.d override values in sysctl.conf when loaded later.
Can sysctl hardening break my applications?
Yes, it can. The most common issues are: disabling user namespaces breaks Chromium and rootless containers, disabling IP forwarding breaks container networking, and disabling TCP SACK may reduce throughput on lossy networks. Always test changes in a staging environment first and deploy incrementally. I can't stress this enough.
How do I know which sysctl parameters my kernel supports?
Run sysctl -a 2>/dev/null | wc -l to see the total count, or sysctl -a 2>/dev/null | grep parameter_name to check a specific one. Parameters vary by kernel version, compiled modules, and loaded LSMs. If a parameter doesn't exist in /proc/sys, the kernel will log an error when you try to apply it — but other parameters will still load just fine.
Should I disable IPv6 for security?
Only if you're 100% certain it's not used anywhere in your environment. A lot of modern services and cloud platforms require IPv6, and disabling it can cause unexpected failures. The better approach is to harden IPv6 by disabling router advertisements and source routing while keeping the protocol stack active.
How often should I review my sysctl hardening configuration?
Review it after every major kernel update, when CIS Benchmarks release new versions (usually annually), after any security incident, and when deploying new workloads like containers or databases that may have different requirements. The automated drift detection with systemd timers or OpenSCAP scanning covered earlier in this guide goes a long way toward reducing the manual review burden.