sysctl Tuning That Earns Its Keep: Network and Kernel Settings for a Busy Server

Most of the sysctl “optimisation” lists you can copy off the internet are cargo cult: a wall of settings pasted from a 2013 forum thread, half of them defaults already, a few of them actively harmful on a modern kernel. This is the opposite of that. Below is a curated /etc/sysctl.d drop-in for a server that actually handles connection volume, with the reason each line exists and the command that proves it took effect.

The target is Ubuntu 24.04, which ships the 6.8 kernel. That matters, because several of the settings people still paste were either fixed as defaults years ago or removed outright. Tuning a 6.8 kernel with advice written for 3.x is how you end up slower, not faster.

The rule before the file

Only change a kernel parameter if you can name the symptom it fixes and measure the before and after. Everything below is here because it addresses a specific, observable failure mode on a busy host: accept queues overflowing, ephemeral ports exhausting, file watchers hitting a ceiling, TIME_WAIT sockets piling up. If you cannot see the symptom in ss, nstat or your metrics, you do not need the setting. The same discipline that makes delivery metrics worth instrumenting applies to kernel tuning: an unmeasured change is a guess wearing a config file.

The drop-in

Put this in /etc/sysctl.d/90-tuning.conf, not in /etc/sysctl.conf. Drop-ins are the supported mechanism, they survive package upgrades, and they keep your changes in one auditable file you can commit to version control.

# /etc/sysctl.d/90-tuning.conf
# Curated kernel tuning for a busy application server.
# Ubuntu 24.04 / kernel 6.8. Apply: sudo sysctl --system

# --- Accept queues -------------------------------------------------
# Ceiling on the listen() backlog. Your application must ALSO pass a
# backlog at least this large to listen(); this value is only the cap.
# The 6.8 default is already 4096 - raise it only under real churn.
net.core.somaxconn = 8192

# Queue of half-open (SYN_RECV) connections awaiting a handshake.
# A small default drops SYNs during connection storms.
net.ipv4.tcp_max_syn_backlog = 8192

# Packets queued on the input side when the NIC delivers faster than
# the kernel drains them (bursts on a 10GbE link).
net.core.netdev_max_backlog = 16384

# --- Ephemeral ports and TIME_WAIT --------------------------------
# Widen the outbound port range (default 32768-60999) so a proxy
# making many upstream calls does not exhaust ephemeral ports.
net.ipv4.ip_local_port_range = 1024 65535

# Reuse TIME_WAIT sockets for new OUTBOUND connections when the
# timestamp check makes it safe. 0=off, 1=on, 2=loopback only (the
# 6.8 default). Set 1 on a busy reverse proxy or API client.
# NOT tcp_tw_recycle - that was removed in kernel 4.12; do not use it.
net.ipv4.tcp_tw_reuse = 1

# --- Keepalive ----------------------------------------------------
# Detect dead peers sooner than the 2-hour (7200s) default so
# half-open connections behind a load balancer do not accumulate.
net.ipv4.tcp_keepalive_time = 300
net.ipv4.tcp_keepalive_intvl = 30
net.ipv4.tcp_keepalive_probes = 5

# --- File handles -------------------------------------------------
# System-wide open-file ceiling. Usually already generous; the limit
# you actually hit first is the PER-PROCESS one (nofile), set in the
# systemd service unit, not here. Documented for completeness.
fs.file-max = 2097152

# inotify watches/instances for file-watching build tools and log
# shippers. The historical 8192 watch default is far too low for a
# large source tree or many watched log files.
fs.inotify.max_user_watches = 524288
fs.inotify.max_user_instances = 512

# --- Hardening ----------------------------------------------------
# SYN cookies protect the accept queue under a SYN flood. Default is
# already 1 - pinned so a careless edit cannot silently disable it.
net.ipv4.tcp_syncookies = 1

# Reverse-path filter: drop packets whose source is not routable back
# out of the arriving interface. 1=strict, 2=loose. Use 2 on a
# multi-homed host with asymmetric routing, or strict filtering
# will silently blackhole legitimate traffic.
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1

Why these, and not the fashionable ones

somaxconn is a ceiling, not a lever. Raising it does nothing unless your application passes a matching backlog to listen(). Nginx, for example, needs listen ... backlog=8192 before the kernel value is reachable. Set the kernel high and leave the application at the default and you have changed nothing.

file-max is almost never your problem. The system-wide ceiling is already in the millions on a modern host. The limit that actually causes “too many open files” is the per-process soft limit, and that is set with LimitNOFILE= in the systemd unit, not with sysctl. If a service is running out of descriptors, fix the unit; changing fs.file-max will feel like action while doing nothing.

tcp_tw_reuse is safe; tcp_tw_recycle is a trap. The two get pasted together in old guides. tcp_tw_recycle broke connections from clients behind NAT and was removed entirely in kernel 4.12, so any line setting it on 24.04 is dead weight at best. tcp_tw_reuse only affects outbound connections and is gated by TCP timestamps, which makes it the right tool when a proxy or a service making heavy upstream calls exhausts ports in TIME_WAIT. If your workload retries a lot of short-lived POSTs upstream, this pairs directly with getting idempotent retries right at the application layer.

rp_filter has a footgun. Strict reverse-path filtering is good hygiene on a single-homed server, but on a multi-homed host with asymmetric routing it will drop valid packets and hand you an intermittent connectivity mystery. Know which you have before you commit to 1.

Confirming it actually took effect

Applying the file is one command. Proving the settings are doing something is the part people skip.

# Reload every drop-in and confirm one value round-tripped
sudo sysctl --system
sysctl net.core.somaxconn net.ipv4.tcp_tw_reuse

# Socket summary - watch TIME_WAIT and total counts
ss -s

# Listening sockets: Recv-Q is the current accept-queue depth,
# Send-Q is the configured backlog. Recv-Q climbing toward Send-Q
# means you are about to drop connections.
ss -ltn

# The honest test: are accept queues overflowing right now?
# Non-zero and climbing means somaxconn or the app backlog is too low.
nstat -az TcpExtListenOverflows TcpExtListenDrops

Those nstat counters are the whole argument in two numbers. If ListenOverflows is flat at zero under load, your accept queue is sized correctly and raising somaxconn further is superstition. If it is climbing, you have a measured problem and a measured fix, and you can show the before and after. That is the difference between tuning and cargo cult.

Keep the drop-in in version control, review it like any other code, and gate it behind the same checks as everything else you ship – the argument for a pre-commit baseline that actually runs applies to kernel config as much as to application code. A tuned kernel that nobody can diff, explain or roll back is not an optimisation. It is a liability with better latency.

Free interactive tool

Website compliance checklist

What your site has to do, based on what it actually does

Answer as much or as little as you like — the list builds as you go. Nothing is stored against your name and no email is required.

Most technology problems are not technology problems. They are control problems.

The systems exist. The investment has been made. The question is whether leadership can understand, direct, evidence, and sustain what those systems produce. Find out where control exists — and where it only appears to.