Most Linux VPS hosts hand you a box with a stock kernel and a generic sysctl profile tuned for “average” hardware that doesn’t exist. If you run a busy web stack, a database, or a VPN endpoint, the default network buffers, file descriptor limits, and socket backlog values will throttle you long before your CPU maxes out. This guide walks through the kernel parameters that actually move the needle on a Linux VPS, in the order you should change them, with the reasoning behind each value so you can adapt it to your own workload instead of copy-pasting a random tuning script off a forum. For a look at which providers actually let you touch these settings, see our full VPS comparison.
Before you touch anything: baseline and back up
Every parameter you change should be justified by a measurement. Grab a baseline first, and keep a copy of the current config so you can roll back without a support ticket.
# Snapshot current tunables
sudo sysctl -a > ~/sysctl-backup-$(date +%F).txt
# Quick capability check
nproc; free -m; ip -brief link
sysctl net.core.somaxconn net.ipv4.tcp_congestion_control fs.file-max
Note the CPU count and available RAM. Tuning values scale with both — a socket buffer setting that’s generous on a 1 GB instance can be reckless on a 512 MB one, because the kernel allocates these buffers per-connection under load.
Network stack: the highest-yield category
For most web-facing VPS workloads, the network stack is where you’ll see the clearest wins. Three settings matter most: the listen backlog, the TCP buffer autotuning ceiling, and the congestion control algorithm.
| Parameter | Default | Suggested | Why |
|---|---|---|---|
net.core.somaxconn | 128 | 4096 | Accept queue depth for busy listeners; 128 drops connections during spikes |
net.ipv4.tcp_max_syn_backlog | 128–1024 | 8192 | Survives SYN floods and bursty client reconnects |
net.core.rmem_max | 212992 | 16777216 | Caps autotuning; BDP on high-latency links needs big windows |
net.core.wmem_max | 212992 | 16777216 | Same, for the send side |
net.ipv4.tcp_congestion_control | cubic | bbr | Better throughput on lossy paths; see the tradeoffs below |
Apply them in a dedicated drop-in file rather than editing /etc/sysctl.conf, so package updates and configuration management tools don’t stomp your work.
sudo tee /etc/sysctl.d/60-vps-network.conf >/dev/null <<'EOF'
net.core.somaxconn = 4096
net.ipv4.tcp_max_syn_backlog = 8192
net.core.rmem_default = 262144
net.core.wmem_default = 262144
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.ipv4.tcp_rmem = 4096 262144 16777216
net.ipv4.tcp_wmem = 4096 262144 16777216
net.ipv4.tcp_slow_start_after_idle = 0
net.ipv4.tcp_mtu_probing = 1
net.ipv4.tcp_fin_timeout = 15
EOF
sudo sysctl --system
Should you switch to BBR?
BBR models the network path instead of reacting to packet loss, which helps a lot on paths with bufferbloat or mobile clients. It’s usually a net positive for content delivery and API traffic. The catch: BBR can be unfair to competing flows on a congested link and adds a little CPU overhead. On a 1-core VPS pushing heavy traffic, test before committing.
sudo modprobe tcp_bbr
sysctl net.ipv4.tcp_available_congestion_control
echo "tcp_bbr" | sudo tee /etc/modules-load.d/bbr.conf
sudo sysctl -w net.ipv4.tcp_congestion_control=bbr
sudo sysctl -w net.core.default_qdisc=fq
File descriptors and process limits
“Too many open files” is the classic symptom of a VPS running an nginx + PHP-FPM or Node stack with defaults intact. The system-wide limit and the per-process limit are separate, and you need to raise both.
sudo tee /etc/sysctl.d/61-vps-files.conf >/dev/null <<'EOF'
fs.file-max = 2097152
fs.inotify.max_user_watches = 524288
fs.inotify.max_user_instances = 8192
EOF
sudo sysctl --system
# Per-service raises belong in the unit or the service conf, e.g. nginx:
# echo "worker_rlimit_nofile 65535;" >> /etc/nginx/nginx.conf
# Verify at runtime
ulimit -n
cat /proc/$(pgrep -o nginx)/limits | grep 'open files'
Watch file-nr under load to confirm you’re not approaching the ceiling: cat /proc/sys/fs/file-nr. The third column is the maximum; if the first column is climbing toward it, raise fs.file-max further.
Memory management: swappiness and overcommit
On a VPS with limited RAM, the default vm.swappiness=60 encourages the kernel to page out anonymous memory early. For a database or cache-heavy workload, that’s a latency disaster. Lower it — but don’t set it to 0, which can cause the OOM killer to fire before swap is ever touched.
sudo tee /etc/sysctl.d/62-vps-memory.conf >/dev/null <<'EOF'
vm.swappiness = 10
vm.vfs_cache_pressure = 50
vm.dirty_ratio = 15
vm.dirty_background_ratio = 5
vm.overcommit_memory = 1
EOF
sudo sysctl --system
- swappiness=10 — keep hot anonymous pages resident; swap only under real pressure.
- vfs_cache_pressure=50 — bias the kernel toward keeping inode and dentry caches, which speeds up filesystem-heavy workloads.
- dirty_ratio / dirty_background_ratio — cap how much dirty page cache the kernel holds before forcing writeback. On slow virtual disks, large dirty buffers create multi-second stalls when they finally flush.
- overcommit_memory=1 — required by Redis and some JVM workloads; it tells the kernel to always allow allocations rather than heuristically refusing them.
Storage scheduler and I/O depth
Virtual disks are usually backed by SSDs or NVMe, and for those devices none (or mq-deadline on older kernels) beats the legacy cfq scheduler. Raising the queue depth also helps when you’re on a host with many vCPUs.
# Check device and current scheduler
lsblk -d -o NAME,ROTA
cat /sys/block/vda/queue/scheduler
# Persist the choice
echo 'ACTION=="add|change", KERNEL=="vd*", ATTR{queue/scheduler}="none"' \
| sudo tee /etc/udev/rules.d/60-iosched.rules
If your VPS uses virtio-blk rather than virtio-scsi, you may also benefit from enabling multiqueue and matching the number of queues to vCPUs — but only after you’ve confirmed the host supports it, since a mismatch can hurt more than help.
Validate with a measurable loop
Never trust a tuning guide — including this one — without measuring on your own box. A simple before/after loop keeps you honest.
# Network throughput baseline (run against a known-fast target)
iperf3 -c <server> -t 20 -P 4
# Connection-handling test under concurrency
wrk -t4 -c400 -d30s https://your-site.example/
# Disk latency
fio --name=randread --ioengine=libaio --direct=1 --rw=randread \
--bs=4k --size=1G --numjobs=4 --time_based --runtime=30 \
--group_reporting
Change one category at a time, re-measure, and keep what wins. If a change makes things worse, revert that drop-in file — this is why we used separate sysctl.d files instead of one monolithic edit.
What not to do
- Don’t disable the firewall or SELinux to “improve performance.” The overhead is negligible on modern kernels and the risk is not.
- Don’t raise
net.ipv4.ip_local_port_rangewildly on a small VPS. It can exhaust memory in the conntrack table under a flood. - Don’t copy enterprise tuning numbers onto a 1 GB instance. Buffer maxima scale with RAM; oversized values cause allocation failures under load.
- Don’t skip a reboot test. Verify your
sysctl.dfiles survive a reboot before you walk away.
Kernel tuning isn’t a magic multiplier — it removes artificial ceilings so your hardware can actually be used. If you’re consistently saturating CPU after these changes, the answer is a bigger instance, not more sysctls. When you do need more headroom, compare plans on our provider comparison page and pick a host that gives you full kernel access.


Leave a Reply
You must be logged in to post a comment.