LINUXOR.SK ... open source notes ...

Linux kernel hardening with sysctl

category: configz · date: 2023-11-29 · updated: 2026-09-11 · author: LALA
noteThis is a security baseline for modern Linux servers, derived from older hardened sysctl configurations used for bastion, load-balancer and vault roles. It intentionally separates security hardening from performance tuning. Test every setting on the exact distribution, kernel and application stack before production rollout.

The recommended design is to keep one common hardening file and add a small role-specific file afterward:

output 2 lines
/etc/sysctl.d/50-kernel-hardening.conf
/etc/sysctl.d/60-server-role.conf

This is easier to audit than maintaining several almost-identical sysctl.conf files.

Apply and verify with:

bash
$ sysctl --system
$ sysctl -a

For an individual setting:

bash
$ sysctl kernel.kptr_restrict

Not every kernel exposes every setting. A missing key should be investigated rather than blindly ignored.

Common security baseline

Create /etc/sysctl.d/50-kernel-hardening.conf:

ini
# -----------------------------------------------------------------------------
# Linux kernel hardening baseline
# Target: modern Linux kernels, 2026
# -----------------------------------------------------------------------------

# -----------------------------------------------------------------------------
# Memory / process information exposure
# -----------------------------------------------------------------------------

# Full address-space layout randomization.
kernel.randomize_va_space = 2

# Restrict exposure of kernel pointers, including to privileged readers.
kernel.kptr_restrict = 2

# Prevent unprivileged users from reading the kernel log buffer.
kernel.dmesg_restrict = 1

# Restrict performance monitoring for unprivileged users.
kernel.perf_event_paranoid = 2

# Disable unprivileged BPF while keeping the setting reversible by an
# administrator. Value 1 is stronger but cannot be reverted until reboot.
kernel.unprivileged_bpf_disabled = 2

# Restrict userfaultfd handling of kernel-mode faults.
vm.unprivileged_userfaultfd = 0

# Restrict ptrace to parent/child relationships on kernels with the Yama LSM.
# Debuggers can still be used for normal child processes.
kernel.yama.ptrace_scope = 1

# -----------------------------------------------------------------------------
# Filesystem protections
# -----------------------------------------------------------------------------

# Do not generate core dumps for setuid/setgid or otherwise protected binaries.
fs.suid_dumpable = 0

# Protect against common link-based attacks in world-writable directories.
fs.protected_hardlinks = 1
fs.protected_symlinks = 1

# Extend protections to FIFOs and regular files in world/group-writable
# sticky directories.
fs.protected_fifos = 2
fs.protected_regular = 2

# -----------------------------------------------------------------------------
# IPv4 / IPv6 network hardening
# -----------------------------------------------------------------------------

# Reject IPv4 source-routed packets.
net.ipv4.conf.all.accept_source_route = 0
net.ipv4.conf.default.accept_source_route = 0

# For IPv6, -1 fully disables acceptance of source-routing headers.
net.ipv6.conf.all.accept_source_route = -1
net.ipv6.conf.default.accept_source_route = -1

# Do not accept ICMP redirects.
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.ipv6.conf.all.accept_redirects = 0
net.ipv6.conf.default.accept_redirects = 0

# Hosts should not send IPv4 ICMP redirects.
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.default.send_redirects = 0

# Basic protections against malformed or broadcast ICMP traffic.
net.ipv4.icmp_echo_ignore_broadcasts = 1
net.ipv4.icmp_ignore_bogus_error_responses = 1

# SYN cookies remain a useful fallback when the SYN backlog overflows.
net.ipv4.tcp_syncookies = 1

# Protect against TIME-WAIT assassination described by RFC 1337.
net.ipv4.tcp_rfc1337 = 1

# Log packets with impossible/spoofed-looking source addresses.
# Consider log-rate limiting on Internet-facing systems.
net.ipv4.conf.all.log_martians = 1
net.ipv4.conf.default.log_martians = 1

Role: bastion / jump host

A bastion should normally behave as an endpoint, not as a router.

Create /etc/sysctl.d/60-server-role.conf:

ini
# Do not route traffic between interfaces.
net.ipv4.ip_forward = 0
net.ipv6.conf.all.forwarding = 0

# Strict reverse-path validation is appropriate when routing is symmetric.
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1

For a high-assurance bastion, consider the following only after validating that troubleshooting and management tools do not need them:

ini
# Only privileged processes may attach with ptrace.
kernel.yama.ptrace_scope = 2

# Disable creation of new io_uring instances completely.
# This reduces kernel attack surface but can break applications that use io_uring.
kernel.io_uring_disabled = 2

kernel.io_uring_disabled = 2 is intentionally not part of the common baseline because some applications legitimately depend on io_uring.

Role: vault / secrets server

A secrets server has essentially the same network posture as a bastion: it should not route traffic.

ini
net.ipv4.ip_forward = 0
net.ipv6.conf.all.forwarding = 0

net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1

For a dedicated secrets appliance with a tightly controlled software stack, the stronger process-isolation options are also reasonable after testing:

ini
kernel.yama.ptrace_scope = 2

# Optional: only if the application stack does not require io_uring.
kernel.io_uring_disabled = 2

Kernel hardening is only one layer for a vault system. Memory locking, service sandboxing, filesystem permissions, encrypted storage, authentication policy and secret lifecycle controls must be handled by the application and service manager as well.

Role: load balancer

Load balancers are different because some designs intentionally route packets, use virtual IP addresses, asymmetric paths or direct-server-return.

The old load-balancer configuration enabled IPv4 forwarding. Keep that only when the architecture actually requires routing.

Typical L3/L4 load-balancer overlay:

ini
# Required only for designs that forward packets at the IP layer.
net.ipv4.ip_forward = 1

# Use loose reverse-path validation when legitimate asymmetric routing is
# possible. Some DSR/policy-routing designs may require per-interface tuning
# or rp_filter=0 instead.
net.ipv4.conf.all.rp_filter = 2
net.ipv4.conf.default.rp_filter = 2

# Allow services to bind a virtual IPv4 address before it is present locally.
# Useful for HA/failover VIP designs.
net.ipv4.ip_nonlocal_bind = 1

If the load balancer forwards IPv6 as well:

ini
net.ipv6.conf.all.forwarding = 1

If it is only a userspace reverse proxy such as HAProxy or NGINX, IP forwarding is normally unnecessary:

ini
net.ipv4.ip_forward = 0
net.ipv6.conf.all.forwarding = 0

Do not use strict rp_filter=1 blindly on a load balancer with asymmetric routing. The Linux kernel documentation explicitly recommends loose mode for complicated/asymmetric routing.

Additional high-assurance options

These controls can reduce kernel attack surface further, but they are operationally significant and should not be enabled blindly.

Disable io_uring

Linux provides:

ini
kernel.io_uring_disabled = 1

to block unprivileged io_uring creation while still allowing privileged processes, or:

ini
kernel.io_uring_disabled = 2

to block creation for all processes.

This is suitable only when application compatibility has been tested.

Disable further kernel module loading

After all required modules are loaded, Linux can permanently disable module load/unload operations until reboot:

ini
kernel.modules_disabled = 1

This is an irreversible runtime setting. It can break hot-plugging, filesystems, drivers, monitoring and recovery workflows. It is therefore not included in the baseline.

Disable kexec loading

Linux also supports:

ini
kernel.kexec_load_disabled = 1

which permanently disables new kexec image loading until reboot. This can be valuable on locked-down appliances but can interfere with kdump or operational recovery procedures.

Validation

Check that all intended values were applied:

bash
$ sysctl --system
$ 
$ sysctl kernel.randomize_va_space
$ sysctl kernel.kptr_restrict
$ sysctl kernel.dmesg_restrict
$ sysctl kernel.unprivileged_bpf_disabled
$ sysctl vm.unprivileged_userfaultfd
$ 
$ sysctl fs.suid_dumpable
$ sysctl fs.protected_hardlinks
$ sysctl fs.protected_symlinks
$ sysctl fs.protected_fifos
$ sysctl fs.protected_regular
$ 
$ sysctl net.ipv4.ip_forward
$ sysctl net.ipv4.conf.all.rp_filter
$ sysctl net.ipv4.conf.all.accept_redirects
$ sysctl net.ipv6.conf.all.accept_redirects

Also verify application behavior, routing, failover and monitoring. A syntactically valid sysctl profile can still be operationally wrong for a particular network design.

References

← configz