← Back to blog

How to Optimize VPS Performance: 10 Proven Techniques

Maria Ilinca Bostan

Most VPS performance problems are configuration problems, not capacity problems. Before adding RAM or vCPU, work through the ten changes below: they cost nothing, apply to almost any Linux VPS, and in combination usually recover more headroom than a plan upgrade would. Measure before and after each one — a change you cannot attribute is a change you cannot keep.

First: find out what is actually slow

Optimising the wrong subsystem is the most common waste of an afternoon. Spend ten minutes establishing the bottleneck.

# overall picture: load, steal time, memory, io wait
top -b -n1 | head -20
vmstat 1 5

# per-device disk latency and utilisation
iostat -xz 1 5

# what is holding memory
ps aux --sort=-%mem | head -10

# network
ss -s

Three numbers tell you where to look. %wa (I/O wait) above about 10% means disk. %st (steal time) consistently above a few percent means the host is oversubscribed and no amount of tuning inside the guest will fix it. Load average persistently above your vCPU count with low I/O wait means CPU. Everything else is detail.

1. Configure swap correctly — and stop pretending you do not need it

A VPS with no swap does not degrade under memory pressure, it invokes the OOM killer and loses a process. A little swap gives the kernel somewhere to put genuinely cold pages.

sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile && sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab

# use it only under real pressure
echo 'vm.swappiness=10'        | sudo tee /etc/sysctl.d/99-tuning.conf
echo 'vm.vfs_cache_pressure=50'| sudo tee -a /etc/sysctl.d/99-tuning.conf
sudo sysctl --system

Size swap at roughly half your RAM up to about 4 GB. On SSD-backed storage the write amplification concern is overstated at swappiness=10; the OOM killer is the greater risk.

2. Tune the web server to the machine you actually have

Default configurations assume a much larger server. On Nginx:

worker_processes auto;          # one per vCPU
worker_rlimit_nofile 65535;
events { worker_connections 4096; multi_accept on; }
http {
    sendfile on; tcp_nopush on; tcp_nodelay on;
    keepalive_timeout 30;
    gzip on; gzip_comp_level 5; gzip_min_length 1024;
    gzip_types text/plain text/css application/json application/javascript text/xml image/svg+xml;
}

For PHP-FPM the killer default is pm = dynamic with a max_children inherited from a much bigger box. Calculate it: available RAM for PHP ÷ average process size. On a 4 GB instance with 60 MB processes and 1 GB reserved for everything else, that is about 50 — not the 5 or the 500 the default gives you. Over-provisioning max_children is how a traffic spike turns into swap death instead of a queue.

3. Add an object cache and a page cache

The cheapest request is the one your application never handles. Redis for object and session caching:

sudo apt install -y redis-server
sudo sed -i 's/^# maxmemory .*/maxmemory 512mb/' /etc/redis/redis.conf
sudo sed -i 's/^# maxmemory-policy .*/maxmemory-policy allkeys-lru/' /etc/redis/redis.conf
sudo systemctl restart redis-server

Setting maxmemory is not optional. An unbounded Redis will grow until it triggers the OOM killer, and the process it kills is frequently not Redis.

Above that, cache whole responses at the proxy. Our Nginx reverse proxy guide covers proxy_cache with stale-while-error, which also makes backend restarts invisible to users.

4. Fix the database before you blame the server

On most application servers the database is the bottleneck and the fix is an index, not hardware.

# MySQL / MariaDB: what is slow
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1;

# PostgreSQL: same idea
ALTER SYSTEM SET log_min_duration_statement = '1000ms';
SELECT pg_reload_conf();

Then size the buffer pool. MySQL's innodb_buffer_pool_size should be roughly 50–70% of RAM on a dedicated database box, considerably less when the application shares the machine. PostgreSQL's shared_buffers wants about 25% of RAM, with effective_cache_size set to around 50–75% so the planner knows what the OS is caching. Run EXPLAIN on the three slowest queries before touching any of this.

5. Turn on HTTP/2, compression and sensible cache headers

HTTP/2 multiplexes requests over one connection, which removes head-of-line blocking for pages with many small assets. It requires TLS in every browser. Add Brotli alongside gzip if your build supports it — typically 15–20% smaller than gzip on text. Then set far-future cache headers on fingerprinted assets and short ones on HTML. This is the highest-leverage work available for perceived speed, and it happens entirely at the edge; our guide to hosting and website speed goes further into the browser side.

6. Tune the kernel network stack

cat <<'EOF' | sudo tee /etc/sysctl.d/99-network.conf
net.core.somaxconn = 4096
net.ipv4.tcp_max_syn_backlog = 4096
net.ipv4.tcp_fin_timeout = 20
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_slow_start_after_idle = 0
net.core.default_qdisc = fq
net.ipv4.tcp_congestion_control = bbr
fs.file-max = 200000
EOF
sudo sysctl --system

BBR is the one worth singling out. On paths with any packet loss — which is most long-distance paths — it typically outperforms the default CUBIC substantially for throughput. somaxconn matters the moment your accept queue overflows, which shows up as connection timeouts under load rather than slowness.

7. Stop running services you do not use

systemctl list-units --type=service --state=running
systemd-analyze blame | head -20

A default cloud image often ships snapd, a print server, a mail transfer agent and a bluetooth daemon. On a 2 GB instance, reclaiming 200–300 MB and a handful of background wakeups is a real percentage of the machine. Disable rather than uninstall where you are unsure.

8. Match the filesystem and I/O scheduler to virtual disks

# virtio devices do their own scheduling; the guest scheduler adds latency
echo none | sudo tee /sys/block/vda/queue/scheduler

# stop recording access times on every read
sudo sed -i 's/\(ext4\s\+\)defaults/\1defaults,noatime/' /etc/fstab

noatime removes a metadata write on every file read — on a read-heavy web server that is a measurable reduction in write IOPS for no downside. Set the scheduler persistently with a udev rule rather than relying on the shell command surviving a reboot.

Know what your storage can actually do

Disk type sets the ceiling for everything above. NVMe reaches far higher IOPS at far lower latency than SATA SSD, and for database and container workloads that difference dominates. Our SSD versus NVMe comparison covers where the gap is real and where it is marketing.

9. Put the server near its users

No amount of tuning recovers 120 ms of physical distance. A German audience served from Virginia pays that round trip on every uncached request, and TLS handshakes pay it several times over. Choosing Frankfurt for German traffic or Warsaw for Polish traffic is a larger single win than every sysctl on this page combined. We run twelve locations across Europe, Turkey and the US East Coast, and choosing a datacenter location covers how to pick.

10. Monitor continuously, not during incidents

sudo apt install -y prometheus-node-exporter
# or the low-tech version, which is better than nothing:
sudo apt install -y sysstat
sudo sed -i 's/false/true/' /etc/default/sysstat
sudo systemctl enable --now sysstat

sar gives you historical CPU, memory, disk and network at ten-minute granularity for the price of one package. Without history, every incident investigation starts from zero and "it got slower" is unfalsifiable. Track the same five numbers over time: load, steal, memory, I/O wait, and p95 response time.

When tuning is not the answer

Three signals mean you have run out of software fixes. Steal time above 5% sustained means the host is oversubscribed — that is a provider problem, not a configuration one. Memory pressure that persists after caching and process limits are right means you need more RAM; no amount of swap tuning substitutes for it. All cores saturated during normal operation means you need more cores, or physical ones. At that point, size up a plan or read VPS versus dedicated servers to see whether bare metal is the right move.

Frequently asked questions

How much can tuning realistically gain?

On an untouched default installation, caching plus correct process limits plus a database index usually make the largest difference by a wide margin. Kernel tuning is worth single-digit percentages by comparison. Do them in that order.

Should I add swap on an SSD-backed VPS?

Yes, with low swappiness. Modern SSD endurance makes the wear argument largely theoretical, and the alternative is losing a process to the OOM killer.

What is steal time and can I fix it?

Steal time is CPU your guest was ready to use but the hypervisor gave to another guest. You cannot fix it from inside the VM. Sustained high steal is grounds for moving instance or provider.

Does more vCPU always help?

Only if your workload is parallel. A single-threaded application on eight vCPU runs at exactly the speed of one core. Check whether you need concurrency or clock speed before buying either.

Test the changes cheaply

Every technique here is worth benchmarking on your own workload rather than trusting a blog post — including this one. Because our plans bill hourly from €0.0056 and deploy in under a minute, standing up a clone, applying the changes and comparing costs a few cents. Then keep what measured better and discard the rest.

Ready in under a minute

Deploy your first server now.

No contracts, no minimums. Start on an Ion KVM VPS at €0.0063 an hour and move to a monthly bare-metal server the day you outgrow it.

$ voxa deploy --plan ion --location amsterdam

KVM VPS billed hourly, capped monthly · Dedicated billed monthly · No setup fee

Included on every plan
Free IPv4 + IPv6
Every VPS
Unmetered traffic
1–10 Gbps
DDoS mitigation
2.5 Tbps
Root / IPMI access
Included
Setup fee
€0.00
Minimum term
None