DIY Debian Router - Part 7: System hardening and kernel tuning

Oct 9, 2025

Introduction

This is Part 7 of the DIY Debian Router series. See Part 1 for the series introduction and links to all parts.

The Linux kernel exposes hundreds of tunable parameters via sysctl, controlling network stack behavior, security policies, and performance characteristics. This part covers networking, kernel hardening, and optimization for our router.

We'll cover:

  • IP forwarding and routing configuration
  • Anti-spoofing mechanisms (ARP security, reverse path filtering)
  • TCP/IP stack hardening (SYN cookies, RFC 1337 protection)
  • Connection tracking optimization
  • DoS mitigation (ICMP rate limiting, SYN flood protection)
  • Kernel security hardening (ASLR, BPF restrictions, dmesg protection)
  • Network performance tuning (buffer sizes, autotuning)

sysctl persistence

Kernel parameters configured via sysctl -w are lost on reboot. For persistence, we'll write parameters to /etc/sysctl.d/99-router.conf, which is automatically loaded at boot by systemd.

The filename prefix 99- ensures this configuration loads after distribution defaults (typically 10- through 50-), allowing overrides of system defaults.

Complete sysctl configuration

/etc/sysctl.d/99-router.conf

Expand to view config
# Sysctl configuration for secure home router

################## ARP Security & Anti-Spoofing ##################

# Only respond to ARP requests for addresses on receiving interface
net.ipv4.conf.all.arp_filter = 1
net.ipv4.conf.default.arp_filter = 1

# Avoid source address not in target subnet when sending ARP requests
net.ipv4.conf.all.arp_announce = 1
net.ipv4.conf.default.arp_announce = 1

# Only reply to ARP requests for IPs configured on incoming interface
net.ipv4.conf.all.arp_ignore = 1
net.ipv4.conf.default.arp_ignore = 1

################## Per-Interface Settings (WAN) ##################

# IPv4 WAN interface hardening
net.ipv4.conf.enp8s0.accept_redirects = 0
net.ipv4.conf.enp8s0.send_redirects = 0
net.ipv4.conf.enp8s0.accept_source_route = 0
net.ipv4.conf.enp8s0.rp_filter = 1
net.ipv4.conf.enp8s0.log_martians = 1
net.ipv4.conf.enp8s0.arp_filter = 1
net.ipv4.conf.enp8s0.arp_announce = 2
net.ipv4.conf.enp8s0.arp_ignore = 1
net.ipv4.conf.enp8s0.proxy_arp = 0
net.ipv4.conf.enp8s0.bootp_relay = 0

# IPv6 WAN interface settings
net.ipv6.conf.enp8s0.accept_ra = 2
net.ipv6.conf.enp8s0.autoconf = 1
net.ipv6.conf.enp8s0.accept_ra_defrtr = 1
net.ipv6.conf.enp8s0.accept_ra_pinfo = 1
net.ipv6.conf.enp8s0.accept_ra_rtr_pref = 1
net.ipv6.conf.enp8s0.accept_redirects = 0
net.ipv6.conf.enp8s0.accept_source_route = 0
net.ipv6.conf.enp8s0.use_tempaddr = 2
net.ipv6.conf.enp8s0.accept_ra_from_local = 0
net.ipv6.conf.enp8s0.dad_transmits = 1

################## Per-Interface Settings (LAN) ##################

# IPv4 LAN interface hardening
net.ipv4.conf.br0.accept_redirects = 0
net.ipv4.conf.br0.send_redirects = 0
net.ipv4.conf.br0.accept_source_route = 0
net.ipv4.conf.br0.rp_filter = 2
net.ipv4.conf.br0.proxy_arp = 0
net.ipv4.conf.br0.arp_filter = 1
net.ipv4.conf.br0.arp_announce = 2
net.ipv4.conf.br0.arp_ignore = 1
net.ipv4.conf.br0.log_martians = 1
net.ipv4.conf.br0.bootp_relay = 0

# IPv6 LAN interface settings
net.ipv6.conf.br0.accept_ra = 0
net.ipv6.conf.br0.autoconf = 0
net.ipv6.conf.br0.accept_redirects = 0
net.ipv6.conf.br0.accept_source_route = 0
net.ipv6.conf.br0.forwarding = 1
net.ipv6.conf.br0.use_tempaddr = 0
net.ipv6.conf.br0.dad_transmits = 1
net.ipv6.conf.br0.accept_ra_from_local = 0

################## IPv4 Forwarding & Routing ##################

# Enable IPv4 forwarding (REQUIRED for router functionality)
net.ipv4.ip_forward = 1

# Disable source routing (security - prevents packet routing manipulation)
net.ipv4.conf.all.accept_source_route = 0
net.ipv4.conf.default.accept_source_route = 0

# Enable reverse path filtering (anti-spoofing)
# Mode 1 = strict mode (recommended for routers with asymmetric routing awareness)
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1

# Disable redirects (security - prevents MITM attacks)
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

# Do not send ICMP redirects (we are a router, but this prevents abuse)
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.default.send_redirects = 0

# Ignore ICMP echo requests to broadcast/multicast (smurf attack protection)
net.ipv4.icmp_echo_ignore_broadcasts = 1

# Ignore bogus ICMP error responses
net.ipv4.icmp_ignore_bogus_error_responses = 1

# Log martian packets (packets with impossible addresses)
net.ipv4.conf.all.log_martians = 1
net.ipv4.conf.default.log_martians = 1

################## IPv6 Forwarding & Routing ##################

# Enable IPv6 forwarding (REQUIRED for IPv6 router functionality)
net.ipv6.conf.all.forwarding = 1
net.ipv6.conf.default.forwarding = 1

# Accept Router Advertisements on WAN interface ONLY
# Note: Forwarding enabled disables RA by default, override per-interface
net.ipv6.conf.all.accept_ra = 0
net.ipv6.conf.default.accept_ra = 0

# Disable IPv6 source routing (security)
net.ipv6.conf.all.accept_source_route = 0
net.ipv6.conf.default.accept_source_route = 0

# Disable IPv6 redirects (security)
net.ipv6.conf.all.accept_redirects = 0
net.ipv6.conf.default.accept_redirects = 0

# Do not accept Router Preference from RA
net.ipv6.conf.all.accept_ra_rtr_pref = 0
net.ipv6.conf.default.accept_ra_rtr_pref = 0

# Do not learn Prefix Information in RA
net.ipv6.conf.all.accept_ra_pinfo = 0
net.ipv6.conf.default.accept_ra_pinfo = 0

# Do not accept default router from RA (we are the router)
net.ipv6.conf.all.accept_ra_defrtr = 0
net.ipv6.conf.default.accept_ra_defrtr = 0

# Don't autoconfigure addresses from RA (except on WAN)
net.ipv6.conf.all.autoconf = 0
net.ipv6.conf.default.autoconf = 0

# Number of Router Solicitations to send (for DHCPv6-PD)
net.ipv6.conf.all.router_solicitations = 3
net.ipv6.conf.default.router_solicitations = 3

################## TCP/IP Stack Hardening ##################

# Enable SYN cookies (SYN flood protection)
net.ipv4.tcp_syncookies = 1

# Increase SYN backlog for better performance under load
net.ipv4.tcp_max_syn_backlog = 4096

# Disable TCP timestamps (privacy - prevents uptime detection)
# Note: Disabling may reduce performance on high-bandwidth connections
# net.ipv4.tcp_timestamps = 0

# Enable TCP selective acknowledgments (performance)
net.ipv4.tcp_sack = 1

# Enable TCP window scaling (performance for high-bandwidth connections)
net.ipv4.tcp_window_scaling = 1

# Protect against time-wait assassination (security)
net.ipv4.tcp_rfc1337 = 1

# Decrease TIME_WAIT timeout (performance - faster port reuse)
net.ipv4.tcp_fin_timeout = 30

# Increase max number of connection tracking entries
net.netfilter.nf_conntrack_max = 262144

# Timeout for established connections (default is usually good)
net.netfilter.nf_conntrack_tcp_timeout_established = 432000

# Enable conntrack helper autoassignment (needed for some protocols like FTP)
# Set to 0 for security, 1 for compatibility
net.netfilter.nf_conntrack_helper = 0

################## Network Performance Tuning ##################

# Increase the maximum socket receive buffer size
net.core.rmem_max = 16777216
net.core.rmem_default = 262144

# Increase the maximum socket send buffer size
net.core.wmem_max = 16777216
net.core.wmem_default = 262144

# Increase Linux autotuning TCP buffer limits
# Min, default, and max number of bytes to use
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216

# Increase the maximum number of packets queued on the INPUT side
net.core.netdev_max_backlog = 5000

# Increase the maximum amount of option memory buffers
net.core.optmem_max = 65536

# Enable MTU probing (helps with PMTUD issues)
net.ipv4.tcp_mtu_probing = 1

# Increase local port range for outgoing connections
net.ipv4.ip_local_port_range = 1024 65535

# Allow reuse of sockets in TIME_WAIT state (performance)
net.ipv4.tcp_tw_reuse = 1

################## Kernel Security ##################

# Restrict kernel logs to root only
kernel.dmesg_restrict = 1

# Restrict access to kernel pointers in /proc
kernel.kptr_restrict = 2

# Disable kernel module loading after boot (optional, uncomment for hardening)
# kernel.modules_disabled = 1

# Enable ASLR (Address Space Layout Randomization)
kernel.randomize_va_space = 2

# Restrict perf events to root
kernel.perf_event_paranoid = 3

# Restrict BPF to privileged users only
kernel.unprivileged_bpf_disabled = 1

# Restrict userfaultfd to privileged users
vm.unprivileged_userfaultfd = 0

################## File System Security ##################

# Restrict symlink following
fs.protected_symlinks = 1
fs.protected_hardlinks = 1

# Restrict FIFO access in sticky directories
fs.protected_fifos = 2

# Restrict regular file access in sticky directories
fs.protected_regular = 2

################## Rate Limiting & DoS Protection ##################

# Limit ICMP rate (packets per second)
net.ipv4.icmp_ratelimit = 100

# Which ICMP types to rate limit (default is all except ping reply)
net.ipv4.icmp_ratemask = 6168

# IPv6 ICMP rate limiting
net.ipv6.icmp.ratelimit = 100

################## Additional Security Settings ##################

# Ignore all ICMP echo requests (uncomment to disable ping entirely)
# net.ipv4.icmp_echo_ignore_all = 1
# net.ipv6.icmp.echo_ignore_all = 1

# Disable core dumps (security - prevents information leakage)
kernel.core_uses_pid = 1
fs.suid_dumpable = 0

# Restrict ptrace to parent process only
kernel.yama.ptrace_scope = 1

################## IPv6 Privacy Extensions ##################

# Enable IPv6 privacy extensions for LAN clients
# This is handled by clients, but router should allow it
net.ipv6.conf.all.use_tempaddr = 2
net.ipv6.conf.default.use_tempaddr = 2

# Temporary address valid lifetime
net.ipv6.conf.all.temp_valid_lft = 86400
net.ipv6.conf.default.temp_valid_lft = 86400

# Temporary address preferred lifetime
net.ipv6.conf.all.temp_prefered_lft = 14400
net.ipv6.conf.default.temp_prefered_lft = 14400

# Maximum number of temporary addresses
net.ipv6.conf.all.max_addresses = 16
net.ipv6.conf.default.max_addresses = 16

################## End of Configuration ##################

Key configuration aspects:

  • IP forwarding enabled (ip_forward=1, ipv6.forwarding=1) to route packets between interfaces
  • Strict reverse path filtering (rp_filter=1) validates source addresses to prevent IP spoofing
  • ARP security (arp_filter, arp_announce, arp_ignore) prevents cache poisoning and routing manipulation
  • Source routing disabled (accept_source_route=0) blocks legacy routing manipulation attacks
  • ICMP redirects blocked (accept_redirects=0, send_redirects=0) prevents man-in-the-middle attacks
  • SYN flood protection (tcp_syncookies=1, tcp_max_syn_backlog=4096) defends against connection exhaustion
  • Connection tracking scaled (nf_conntrack_max=262144) supports up to 262k simultaneous connections (~75 MB RAM)
  • Socket buffers increased (16 MB max with TCP autotuning) for gigabit throughput optimization
  • Ephemeral port range expanded (1024-65535) provides ~64k ports for high-concurrency scenarios
  • Full ASLR enabled (randomize_va_space=2) randomizes memory layout to hinder exploits
  • Kernel pointer hiding (kptr_restrict=2) prevents memory layout disclosure even to root
  • Unprivileged BPF disabled (unprivileged_bpf_disabled=1) restricts BPF to root
  • Filesystem protections (protected_symlinks, protected_fifos) prevent /tmp attacks against privileged processes
  • Core dump restrictions (suid_dumpable=0) disables dumps for setuid programs to prevent credential leakage
  • Per-interface boundaries: WAN uses strict filtering and accepts RAs; LAN uses loose filtering for asymmetric routing support

Load the configuration immediately:

sysctl -p /etc/sysctl.d/99-router.conf

Verify global settings:

sysctl net.ipv4.ip_forward
sysctl net.ipv6.conf.all.forwarding

Both should return 1.

Verify per-interface settings:

sysctl net.ipv4.conf.enp8s0.rp_filter
sysctl net.ipv6.conf.enp8s0.accept_ra
sysctl net.ipv4.conf.br0.rp_filter
sysctl net.ipv6.conf.br0.accept_ra

Reboot to ensure persistence:

reboot

After reboot, verify settings persist:

sysctl net.ipv4.ip_forward
sysctl net.ipv6.conf.all.forwarding

Testing and verification

IP forwarding test

From a LAN client, test Internet connectivity:

ping 8.8.8.8
curl -I https://google.com

Both should succeed. If forwarding is disabled, traffic does not traverse the router.

Anti-spoofing test (simulated)

From the router, attempt to send packets with a spoofed source address (requires raw socket capability):

apt install hping3
hping3 -a 10.0.0.1 -S -c 3 google.com -p 80

With rp_filter=1, the kernel drops these packets at the source. If you enabled logging, check dmesg for martian packet logs.

Connection tracking table inspection

Monitor connection tracking table size:

cat /proc/sys/net/netfilter/nf_conntrack_count
cat /proc/sys/net/netfilter/nf_conntrack_max

Under normal load, count should be well below max. If count approaches max, increase nf_conntrack_max.

Troubleshooting common issues

High latency on gigabit connections

If throughput is good but you observe latency spikes, the cause may be insufficient buffer sizes or queue depth.

To fix, increase buffer sizes and queue depth, e.g.,:

net.core.netdev_max_backlog = 10000
net.ipv4.tcp_rmem = 4096 131072 33554432

IPv6 clients not receiving RAs

If IPv6 stopped working after applying sysctl configuration, it may be due to accept_ra=0 overriding systemd-networkd settings.

To fix, ensure WAN interface configuration includes IPv6AcceptRA=yes in /etc/systemd/network/20-wan.network:

[IPv6AcceptRA]
UseRoutes=yes

Advanced tuning considerations

High-concurrency scenarios (>10,000 Connections)

For routers serving thousands of connections:

net.netfilter.nf_conntrack_max = 1048576  # 1M connections
net.ipv4.tcp_max_syn_backlog = 16384
net.ipv4.ip_local_port_range = 1024 65535
fs.file-max = 2097152  # Max open files

Tradeoff: Higher memory, e.g., 1M tracked connections ≈ 300 MB RAM.

Low-Latency optimization

For real-time applications (video conferencing, gaming, etc):

net.ipv4.tcp_low_latency = 1
net.core.busy_poll = 50
net.core.busy_read = 50

Tradeoff: Increased CPU usage in exchange for reduced latency.

You can find more information in this article.

Performance baseline

After applying all configurations, establish a performance baseline using iperf3:

# Server on LAN:
iperf3 -s

# Client on another LAN device:
iperf3 -c [server IP] -t 60

You should see near line-rate (900+ Mbps for gigabit Ethernet).

Check latency by doing a simple ping from a client:

ping -c 100 8.8.8.8

Should see <10 ms for most residential Internet connections.

Next steps

With our sysctl configuration, our router now provides solid networking with a strong security posture. You could stop here. But that would be boring, continue to Part 8 to set up WireGuard for secure remote access.

RSS
https://yusefkarim.absurdum.ca/posts/feed.xml