Skip to content
Back to Blog
Performance11 min read

Advanced VPS Hosting Performance Tuning: 8 Moves That Work

Skip provider comparisons. These eight advanced techniques squeeze maximum performance from any VPS through kernel tuning, I/O scheduling, and traffic shaping.

Written by Abdul AbrorTechnical Hosting Support Engineer
Advanced VPS Hosting Performance Tuning: 8 Moves That Work
On this page

You already picked a VPS provider. Now comes the real work: making it fast.

Most hosting reviews stop at advertised specs and synthetic benchmarks. But the gap between a default VPS and a properly tuned one can be 40-60% in real application throughput. I've spent years handling support tickets where clients complained about "slow servers" that were actually misconfigured at the OS level, not undersized. These eight moves assume you're comfortable editing system files and restarting services; they're not for beginners.

Kernel parameter tuning for network workloads

The default Linux network stack is optimized for desktop use, not server traffic. Your VPS is probably dropping packets under moderate load without telling you.

Start by checking current connection tracking limits:

sysctl net.netfilter.nf_conntrack_max
sysctl net.netfilter.nf_conntrack_count

If the count approaches the max during peak traffic, you're silently losing connections. I typically set nf_conntrack_max to at least 262144 on VPS instances handling more than 100 concurrent users. Add these to /etc/sysctl.conf:

net.netfilter.nf_conntrack_max = 262144
net.netfilter.nf_conntrack_tcp_timeout_established = 3600
net.core.somaxconn = 4096
net.core.netdev_max_backlog = 5000
net.ipv4.tcp_max_syn_backlog = 8096

The somaxconn value controls listen queue depth. Web servers with high request rates hit the default limit of 128 instantly. Apply changes with sysctl -p and monitor netstat -s | grep -i listen for overflow counters.

TCP window scaling matters more than most think. For VPS instances serving international traffic, increase buffer sizes:

net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216

These settings let TCP use the full bandwidth-delay product on high-latency paths. The difference shows up in time-to-first-byte for users 150+ ms away.

Block device scheduler selection

Your VPS uses virtualized storage, usually backed by SSD or NVMe. But the I/O scheduler might still be set for spinning disks.

Check current scheduler:

cat /sys/block/vda/queue/scheduler

If you see [cfq] or [deadline], you're using legacy schedulers designed for rotational media. Modern kernels offer mq-deadline, kyber, and none. For SSD-backed VPS instances, none (also called noop on older kernels) often wins because the hypervisor layer already handles I/O scheduling.

Switch it:

echo none > /sys/block/vda/queue/scheduler

Make it permanent by adding elevator=none to your kernel command line in /etc/default/grub, then run update-grub. I've seen database query times drop 15-20% just from this change on busy MySQL servers.

Read-ahead settings also matter. The default 128 KB is conservative:

blockdev --setra 8192 /dev/vda

That sets read-ahead to 4 MB, which helps sequential read workloads like log processing and backup operations. For random-read databases, keep it at 256-512 KB instead.

CPU governor and frequency scaling

Most VPS providers expose CPU frequency control to guests. The default powersave governor prioritizes energy efficiency over performance.

Check current state:

cpupower frequency-info

If you see powersave and your workload needs consistent low latency, switch to performance:

cpupower frequency-set -g performance

This keeps CPU cores at maximum frequency instead of ramping up reactively. Request latency drops because you eliminate the 1-3 ms lag while the governor scales up frequency. The trade-off? Slightly higher cost if your provider meters CPU cycles precisely, though most don't.

For mixed workloads, schedutil offers a middle ground. It scales frequency based on the scheduler's load tracking, responding faster than ondemand.

Transparent Huge Pages configuration

THP can hurt database performance badly, especially Redis and MongoDB. The kernel tries to promote 4 KB pages to 2 MB pages in the background, causing unpredictable latency spikes.

Check THP status:

cat /sys/kernel/mm/transparent_hugepage/enabled

If you see [always], disable it:

echo never > /sys/kernel/mm/transparent_hugepage/enabled
echo never > /sys/kernel/mm/transparent_hugepage/defrag

Add those commands to /etc/rc.local or create a systemd service to persist across reboots. Database vendors recommend this almost universally. I saw one client's Redis latency P99 drop from 18 ms to under 2 ms after disabling THP.

For general web serving workloads, madvise mode offers a compromise—applications can explicitly request huge pages, but the kernel won't promote them automatically.

Traffic shaping and QoS for multi-tenant services

If you're running multiple sites or services on one VPS, traffic from one can starve the others. Most admins skip traffic control entirely or use crude iptables rate limits.

The tc (traffic control) subsystem gives you proper queuing disciplines. Here's a simple setup that prioritizes SSH and DNS while allowing HTTP to burst:

tc qdisc add dev eth0 root handle 1: htb default 30
tc class add dev eth0 parent 1: classid 1:1 htb rate 900mbit
tc class add dev eth0 parent 1:1 classid 1:10 htb rate 100mbit ceil 900mbit prio 1
tc class add dev eth0 parent 1:1 classid 1:20 htb rate 100mbit ceil 900mbit prio 2
tc class add dev eth0 parent 1:1 classid 1:30 htb rate 700mbit ceil 900mbit prio 3

tc filter add dev eth0 protocol ip parent 1:0 prio 1 u32 match ip dport 22 0xffff flowid 1:10
tc filter add dev eth0 protocol ip parent 1:0 prio 1 u32 match ip dport 53 0xffff flowid 1:10
tc filter add dev eth0 protocol ip parent 1:0 prio 2 u32 match ip dport 443 0xffff flowid 1:20

This creates three classes with different priorities. SSH and DNS get class 1:10 (highest priority, guaranteed 100 Mbit, can burst to full line rate). HTTPS gets middle priority. Everything else defaults to class 1:30.

The htb (Hierarchical Token Bucket) qdisc lets classes borrow bandwidth from each other when available. It's miles better than simple rate limiting because it prevents buffer bloat while maintaining fairness.

Memory and OOM killer tuning

The out-of-memory killer's default behavior is unpredictable. It might kill your database instead of the PHP worker that leaked memory.

First, disable memory overcommit if you run known-stable services:

sysctl vm.overcommit_memory=2
sysctl vm.overcommit_ratio=80

This tells the kernel to reject allocations that would exceed 80% of physical RAM plus swap. Applications get allocation failures instead of the entire system thrashing when memory runs out.

For critical processes, set oom_score_adj to protect them:

echo -1000 > /proc/$(pidof mysqld)/oom_score_adj

Negative values make a process less likely to be killed. I use -1000 for databases and -500 for web servers. Background workers and cron jobs get positive values. This saved me during a client incident where a log rotation script filled /tmp and triggered OOM; the database survived while the script got killed.

Check which processes are candidates:

for pid in /proc/[0-9]*; do
    printf "%d\t%s\n" $(cat $pid/oom_score 2>/dev/null || echo 0) $(cat $pid/comm 2>/dev/null || echo "?")
done | sort -rn | head -20

Anything scoring above 500 is vulnerable. Adjust accordingly.

Filesystem mount options for performance

Default ext4 mount options prioritize data safety over speed. If you have offsite backups and can tolerate a few seconds of data loss during a crash, you can trade safety for IOPS.

Current mount options:

mount | grep /dev/vda

Add these to /etc/fstab for the root partition:

/dev/vda1  /  ext4  defaults,noatime,commit=60,barrier=0  0  1

Here's what each does: - noatime: Skip updating access timestamps on reads. Massive win for file-heavy workloads. - commit=60: Flush dirty pages every 60 seconds instead of 5. Reduces write amplification. - barrier=0: Disable write barriers. Only safe if you have battery-backed cache or accept risk.

Remount to test:

mount -o remount /

I've measured 30-40% improvement in WordPress dashboard load times on VPS instances serving media-heavy sites. The noatime option alone is worth it and carries zero risk.

For /tmp, use tmpfs to keep temporary files in RAM:

tmpfs  /tmp  tmpfs  defaults,size=2G,mode=1777  0  0

Just make sure your VPS has RAM to spare.

Process and connection limits

Systemd and ulimit defaults are set for desktop workloads. A busy web server hits them fast.

Check current limits:

ulimit -a

The open files line is usually 1024, way too low for Nginx or Apache with many simultaneous connections. Increase it system-wide in /etc/security/limits.conf:

*  soft  nofile  65535
*  hard  nofile  65535

For systemd services, limits go in the unit file. Edit /etc/systemd/system/nginx.service.d/limits.conf:

[Service]
LimitNOFILE=65535
LimitNPROC=32768

Reload systemd and restart the service:

systemctl daemon-reload
systemctl restart nginx

Verify:

cat /proc/$(pidof nginx | awk '{print $1}')/limits

The Max open files line should show 65535. I once spent an hour troubleshooting intermittent 502 errors that turned out to be Nginx hitting the file descriptor limit during traffic bursts. Logs showed nothing because the error happened in the kernel, not the application.

Also raise net.core.somaxconn if you haven't already (covered in section one). These limits work together.

What to measure and when to stop

You can tune forever. Know when you're done.

Before making changes, capture baseline metrics:

vmstat 1 60 > baseline_vmstat.txt
sar -n DEV 1 60 > baseline_network.txt
iostat -x 1 60 > baseline_io.txt

Run your application's typical workload—real traffic is best, but synthetic load tests work if they're realistic. After each tuning change, repeat the measurements and compare. If you don't see at least 5-10% improvement, the change probably isn't worth the maintenance complexity.

Watch for regressions too. I once saw a client push net.ipv4.tcp_rmem so high that the kernel spent more time managing buffers than moving data. Bigger isn't always better.

Stop tuning when: 1. Application response time meets your SLA 2. CPU, memory, and I/O show headroom during peak load 3. Error rates (timeouts, connection refused, OOM kills) are near zero 4. Further changes yield less than 5% improvement

Don't optimize for benchmarks. Optimize for your actual traffic patterns.

Frequently asked questions

Do these tuning changes survive kernel updates?
Settings in /etc/sysctl.conf persist. I/O scheduler and CPU governor settings might revert if you change the kernel, so script them into /etc/rc.local or a systemd unit that runs at boot.

Will my VPS provider reset these on reboot?
No, unless you're on a managed hosting plan where the provider controls OS configuration. Unmanaged VPS instances let you change anything.

Which change gives the biggest performance gain?
Disabling Transparent Huge Pages for databases and fixing the I/O scheduler show immediate impact. Network stack tuning matters most for high-traffic web applications.

Can these settings hurt performance?
Yes, if applied blindly. The TCP buffer and connection tracking settings assume you have available RAM. Disabling write barriers risks data loss during crashes. Test each change and monitor.

How do I benchmark my VPS against competitors?
Use real application workloads, not synthetic benchmarks. Deploy your actual stack and measure request latency, throughput, and resource usage under load. Synthetic benchmarks favor different hardware profiles than production workloads.

Start with kernel parameters and I/O scheduler

If you apply just two changes from this list, make them kernel network tuning and I/O scheduler selection. They're low-risk and show immediate improvement on almost every VPS workload I've encountered.

The rest depend on your specific use case. Databases benefit most from disabling THP and tuning memory overcommit. High-traffic web servers need process limits and traffic shaping. CPU-intensive workloads want the performance governor.

Document what you change and why. Six months from now, you'll forget why you set nf_conntrack_max to 262144, and your replacement admin will thank you for leaving notes.