VPS Performance Tuning: CPU Governor, I/O Scheduler, Memory and Network Settings That Measurably Help

Default Linux settings are tuned for laptops and general-purpose servers, not for a virtual machine competing with neighbours on a shared host. The four settings below are the ones that produce measurable throughput gains on real VPS hardware, in the order you should change them. Each entry lists the parameter, the value worth trying, and the workload where it actually helps.

Before tuning, confirm what the hardware can deliver. If the underlying host is oversubscribed, no kernel parameter will rescue you — check the specs and pricing here to see what each tier promises.

1. CPU Governor: Remove the Ramp-Up Delay

The ondemand governor scales frequency up only after load appears, which adds latency to short bursts. For a VPS, idle savings are irrelevant — you pay for the vCPU whether it runs fast or slow. Set the governor to performance:

echo performance | tee /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor

Persist it with a cpufrequtils default file or a systemd unit. On a 4 vCPU AMD EPYC instance, this moved a request-loop benchmark by roughly 10–15% in back-to-back runs. If your provider exposes no cpufreq interface, skip this step — many hypervisors pin frequency regardless.

2. I/O Scheduler: Match the Device, Not the Habit

For NVMe-backed storage, none (or mq-deadline on older kernels) beats the legacy bfq for database and web workloads because the device itself handles queuing efficiently. For spinning disks, keep mq-deadline. Check what you have, then set it:

cat /sys/block/nvme0n1/queue/scheduler
echo none > /sys/block/nvme0n1/queue/scheduler

Measure the change with fio using a random-read job at a queue depth your application actually uses. A scheduler swap that improves a synthetic deep-queue test but hurts your single-threaded web app is a regression, not a win.

3. Memory: Tune swappiness and Overcommit Deliberately

vm.swappiness defaults to 60, which pushes pages to swap too early on a server with adequate RAM. Lowering it to 10 keeps hot application memory resident and treats swap as a last resort rather than a routine overflow:

sysctl -w vm.swappiness=10
sysctl -w vm.vfs_cache_pressure=50

Do not set swappiness to 0 on a VPS — that can trigger the OOM killer instead of a slow, survivable swap. Pair this with a correctly sized swap file measured against your largest working set, and validate under load rather than assuming lower is always better.

4. Network Stack: Buffer Sizing and Backlogs

On high-latency links, the default TCP receive buffer is too small to fill the bandwidth-delay product. Raising the maximums helps throughput on long-distance connections without harming local traffic:

sysctl -w net.core.rmem_max=16777216
sysctl -w net.core.wmem_max=16777216
sysctl -w net.ipv4.tcp_rmem="4096 87380 16777216"
sysctl -w net.ipv4.tcp_wmem="4096 65536 16777216"
sysctl -w net.core.somaxconn=4096

If throughput is the goal rather than latency, also enable BBR congestion control. Verify each change with a repeatable test and write the final values to /etc/sysctl.d/99-tuning.conf so they survive reboots.

Verifying That Tuning Actually Helped

Every parameter above is a hypothesis. Confirm it with a before/after measurement using the same load, the same data, and at least three runs. Record baseline numbers in a text file alongside the change so a future operator knows why a value was set. Tuning without a measurement log is indistinguishable from superstition, and the fastest way to lose performance gains is to change three settings at once and attribute the improvement to the wrong one.

Persisting Changes Without Breaking the Next Boot

The sysctl values set above vanish on reboot unless written to a file. Use a single drop-in rather than editing the distribution’s main configuration, so an upgrade never overwrites your work and so you can revert by deleting one file:

/etc/sysctl.d/99-tuning.conf
# add one key=value per line, then:
sysctl --system

For the I/O scheduler, write a small systemd unit or a udev rule keyed to the device path, since the scheduler file is per-device and does not persist through a reboot on its own. Test by rebooting the instance and re-reading each value — a tuning step you never verified after a restart is a tuning step you may not actually have.

When Tuning Stops Helping

There is a ceiling past which configuration cannot compensate for insufficient resources. If CPU steal stays above 5% under normal load, the host is oversubscribed and you need a plan with guaranteed cores. If random-write IOPS stay low no matter how you set the scheduler, the storage is tiered and the fix is a local-NVMe tier. Recognising this boundary saves days of pointless tuning.

Ordering the Changes to Avoid Confusion

Apply the four groups in the order presented, and stop to measure after each one. The governor is free to change and reversible in seconds. The scheduler affects I/O for every process, so an unexpected regression there can look like an application bug. Memory settings change behaviour only under pressure, which means a mistake may not surface until a traffic spike. Network buffer changes are the most workload-specific of all. Tuning in this order isolates the cause when something goes wrong, and it keeps you from attributing a network improvement to a memory change you made an hour earlier.

Give each change at least one full load cycle before judging it. A tuning value that looks neutral during quiet hours can collapse under peak traffic, and the only way to see that is to observe it at peak.

Start with the governor and scheduler, measure, then move to memory and network — and revisit the whole set whenever you migrate to new hardware, since different providers expose different storage and CPU behaviour.

Leave a Reply