How to Speed Up Your VPS: Caching, Swap, and Kernel Tuning for Maximum Performance
A $20/month VPS with the right configuration often outperforms a $100/month VPS with default settings. Most server performance problems are not hardware problems — they’re configuration problems. Nginx serving uncompressed files, PHP-FPM running with default pool sizes, a kernel not using Google’s BBR algorithm, no swap space for memory overflow — these are all common default states that leave significant performance on the table.
This guide walks through the most impactful VPS performance optimizations in 2025: caching at multiple layers, swap configuration, TCP/kernel tuning, PHP-FPM optimization, and database tuning — all with copy-paste commands for Ubuntu 22.04/24.04.
Before You Optimize: Establish a Baseline
Optimization without measurement is guesswork. Before making any changes, record your current performance metrics so you can quantify the improvement each change delivers.
# CPU and load average
top -bn1 | head -5
# Memory usage
free -h
# Disk I/O performance (install if needed: sudo apt install fio -y)
fio --name=randread --ioengine=libaio --iodepth=16 --rw=randread --bs=4k --size=100M --numjobs=4 --runtime=30 --group_reporting
# Web server benchmark (install if needed: sudo apt install apache2-utils -y)
ab -n 10000 -c 100 http://localhost/
# Current sysctl settings
sysctl -a | grep -E "tcp_congestion|net.core|vm.swappiness"
Save these numbers. After each optimization, re-run the relevant tests to measure actual improvement.
Part 1: Nginx Performance Tuning
Nginx’s default configuration is intentionally conservative. These changes unlock its full potential for production workloads.
Worker Processes and Connections
sudo nano /etc/nginx/nginx.conf
worker_processes auto; # Use all available CPU cores
worker_rlimit_nofile 65535; # Max open files per worker
events {
worker_connections 4096; # Max simultaneous connections per worker
multi_accept on; # Accept multiple connections at once
use epoll; # Best I/O method for Linux
}
HTTP Block Optimizations
http {
# Basic performance
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
keepalive_requests 1000;
server_tokens off;
# File descriptor cache
open_file_cache max=10000 inactive=30s;
open_file_cache_valid 60s;
open_file_cache_min_uses 2;
open_file_cache_errors on;
# Buffer tuning
client_body_buffer_size 128k;
client_max_body_size 50m;
client_header_buffer_size 1k;
large_client_header_buffers 4 16k;
# Gzip compression
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 5;
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_min_length 256;
gzip_types
application/atom+xml application/javascript application/json
application/rss+xml application/vnd.ms-fontobject
application/x-font-ttf application/x-web-app-manifest+json
application/xhtml+xml application/xml font/opentype
image/svg+xml image/x-icon text/css text/plain text/x-component;
}
FastCGI Page Cache (for WordPress and PHP sites)
FastCGI caching stores fully rendered PHP pages at the Nginx level — bypassing PHP and MySQL entirely for cached requests. This is the single most impactful optimization for WordPress sites.
# In nginx.conf http block — define the cache zone
fastcgi_cache_path /var/cache/nginx/fastcgi
levels=1:2
keys_zone=PHPCACHE:100m
inactive=60m
max_size=1g;
# In your server block
fastcgi_cache_key "$scheme$request_method$host$request_uri";
# Don't cache for logged-in users or POST requests
set $no_cache 0;
if ($request_method = POST) { set $no_cache 1; }
if ($query_string != "") { set $no_cache 1; }
if ($http_cookie ~* "wordpress_logged_in") { set $no_cache 1; }
location ~ \.php$ {
fastcgi_pass unix:/var/run/php/php8.3-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
fastcgi_cache PHPCACHE;
fastcgi_cache_valid 200 60m;
fastcgi_cache_bypass $no_cache;
fastcgi_no_cache $no_cache;
add_header X-FastCGI-Cache $upstream_cache_status;
}
# Create the cache directory
sudo mkdir -p /var/cache/nginx/fastcgi
sudo chown www-data:www-data /var/cache/nginx/fastcgi
sudo nginx -t && sudo systemctl reload nginx
After enabling, check cache hits with: curl -I https://yourdomain.com | grep X-FastCGI-Cache — you should see HIT on repeat requests.
Part 2: Redis Object Caching
Redis stores frequently accessed data in memory — eliminating redundant database queries. For WordPress, it reduces database load by 60–90% on active sites. For web applications, it’s equally transformative for session storage and query caching.
Install Redis
sudo apt install redis-server -y
sudo systemctl enable redis-server
sudo systemctl start redis-server
Tune Redis for a VPS (low memory usage)
sudo nano /etc/redis/redis.conf
# Bind to localhost only (don't expose Redis externally)
bind 127.0.0.1
# Set max memory (adjust to ~20% of your VPS RAM)
maxmemory 512mb
# Eviction policy — remove least recently used keys when full
maxmemory-policy allkeys-lru
# Disable persistence if you only need a cache (faster, no disk writes)
save ""
appendonly no
sudo systemctl restart redis-server
# Test Redis is working
redis-cli ping # Should return: PONG
WordPress Redis Integration
Install the Redis Object Cache plugin in WordPress, then add to wp-config.php:
define('WP_REDIS_HOST', '127.0.0.1');
define('WP_REDIS_PORT', 6379);
define('WP_CACHE', true);
Activate the plugin and enable object cache. Database query counts on a typical WordPress page drop from 30–50 queries to 5–10 after Redis is active.
Part 3: PHP-FPM Tuning
PHP-FPM’s default pool settings are far too conservative for a VPS. These settings govern how many PHP workers are available — too few and requests queue up; too many and you exhaust RAM.
sudo nano /etc/php/8.3/fpm/pool.d/www.conf
; Process management — dynamic is best for most VPS setups
pm = dynamic
; Maximum number of PHP workers
; Formula: (Total RAM - OS/DB overhead) / PHP worker memory usage
; Example: (4096MB - 1024MB overhead) / 50MB per worker = ~60 max
pm.max_children = 50
; Workers to start on launch
pm.start_servers = 10
; Minimum idle workers to keep available
pm.min_spare_servers = 5
; Maximum idle workers (prevents too many idle processes)
pm.max_spare_servers = 20
; Recycle workers after this many requests (prevents memory leaks)
pm.max_requests = 500
; Slow log — log PHP processes slower than 5 seconds
slowlog = /var/log/php-fpm-slow.log
request_slowlog_timeout = 5s
sudo systemctl restart php8.3-fpm
PHP.ini Performance Settings
sudo nano /etc/php/8.3/fpm/php.ini
; Memory
memory_limit = 256M
; OPcache — caches compiled PHP bytecode in memory (huge performance win)
opcache.enable=1
opcache.memory_consumption=256
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=10000
opcache.revalidate_freq=2
opcache.save_comments=1
opcache.fast_shutdown=1
; Execution limits
max_execution_time = 60
max_input_time = 60
; Upload limits
upload_max_filesize = 64M
post_max_size = 64M
sudo systemctl restart php8.3-fpm
OPcache alone typically delivers a 30–50% reduction in PHP response times by eliminating repeated parsing and compilation of PHP files.
Part 4: MySQL / MariaDB Tuning
Database performance is often the primary bottleneck for web applications. These settings tune MariaDB/MySQL for a 4 GB RAM VPS:
sudo nano /etc/mysql/mariadb.conf.d/50-server.cnf
[mysqld]
# InnoDB buffer pool — cache frequently accessed data and indexes in RAM
# Set to ~50-70% of available RAM after OS and app overhead
innodb_buffer_pool_size = 1G
# Multiple buffer pool instances for better concurrency
innodb_buffer_pool_instances = 2
# Log file size — larger = fewer flushes = better performance
innodb_log_file_size = 256M
# Flush method — O_DIRECT avoids double buffering with OS cache
innodb_flush_method = O_DIRECT
# Reduce fsync calls (slightly less durable but significantly faster)
innodb_flush_log_at_trx_commit = 2
# Query cache (MariaDB only — removed in MySQL 8.0)
query_cache_type = 1
query_cache_size = 64M
query_cache_limit = 2M
# Connection settings
max_connections = 150
thread_cache_size = 16
table_open_cache = 4000
# Temporary table sizes (reduce disk temp tables)
tmp_table_size = 64M
max_heap_table_size = 64M
sudo systemctl restart mariadb
# Verify buffer pool is active
mysql -e "SHOW ENGINE INNODB STATUS\G" | grep "Buffer pool size"
Part 5: Swap Configuration
Swap acts as overflow memory — when RAM is exhausted, the OS moves inactive data to disk. Without swap, your server will kill processes (OOM killer) when it runs out of RAM. With too much swap, it uses disk constantly and the server becomes sluggish.
Check current swap
free -h
swapon --show
Create a swap file (if none exists)
# Create a 2 GB swap file (recommended: 1-2x RAM for VPS under 8 GB)
sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
# Make it permanent across reboots
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
# Verify
free -h
Tune Swappiness
vm.swappiness controls how aggressively the kernel moves data from RAM to swap. The default (60) is too aggressive for a VPS — it swaps too early, slowing performance unnecessarily.
# Temporarily
sudo sysctl vm.swappiness=10
# Permanently
echo "vm.swappiness=10" | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
A swappiness of 10 means the kernel only starts swapping when RAM usage reaches 90% — keeping applications in fast RAM as long as possible.
Part 6: Kernel TCP Tuning with BBR
BBR (Bottleneck Bandwidth and Round-trip propagation time) is Google’s TCP congestion control algorithm. It significantly improves throughput on high-latency or lossy connections — exactly the conditions users experience when connecting to your VPS from across the country or around the world.
Enable BBR
# Check current congestion control algorithm
sysctl net.ipv4.tcp_congestion_control
# Enable BBR
echo "net.core.default_qdisc=fq" | sudo tee -a /etc/sysctl.conf
echo "net.ipv4.tcp_congestion_control=bbr" | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
# Verify BBR is active
sysctl net.ipv4.tcp_congestion_control
# Should output: net.ipv4.tcp_congestion_control = bbr
Additional Network Buffer Tuning
sudo nano /etc/sysctl.conf
Add these lines:
# Increase network buffer sizes for high-throughput connections
net.core.rmem_max = 134217728
net.core.wmem_max = 134217728
net.core.rmem_default = 31457280
net.core.wmem_default = 31457280
net.ipv4.tcp_rmem = 4096 87380 134217728
net.ipv4.tcp_wmem = 4096 65536 134217728
# Increase the connection backlog queue
net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 65535
# Reuse closed TIME_WAIT connections faster
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 15
# Increase local port range for outbound connections
net.ipv4.ip_local_port_range = 1024 65535
sudo sysctl -p
Part 7: File System and I/O Tuning
Use the deadline or mq-deadline I/O scheduler
# Check current scheduler
cat /sys/block/sda/queue/scheduler
# Set mq-deadline for SSDs (best for VPS with SSD storage)
echo mq-deadline | sudo tee /sys/block/sda/queue/scheduler
# Make it permanent
sudo nano /etc/udev/rules.d/60-scheduler.rules
ACTION=="add|change", KERNEL=="sd[a-z]", ATTR{queue/rotational}=="0", ATTR{queue/scheduler}="mq-deadline"
Increase inotify limits (for applications watching many files)
echo "fs.inotify.max_user_watches=524288" | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
Measuring the Impact
After applying all optimizations, re-run your baseline benchmarks:
# Web server benchmark — compare before and after
ab -n 10000 -c 100 http://localhost/
# Check cache hit rate
curl -I https://yourdomain.com | grep X-FastCGI-Cache
# Database performance
mysqlslap --user=root --password --auto-generate-sql --concurrency=50 --iterations=10
# Memory usage after tuning
free -h
# Confirm BBR is active and network buffers are set
sysctl net.ipv4.tcp_congestion_control
sysctl net.core.rmem_max
Performance Optimization Summary
| Optimization | Typical Improvement | Effort |
|---|---|---|
| Nginx FastCGI cache | 5–50x faster PHP page delivery | Medium |
| Redis object cache | 60–90% fewer DB queries | Low |
| PHP OPcache | 30–50% faster PHP execution | Low |
| PHP-FPM pool tuning | Eliminates request queuing | Low |
| MariaDB buffer pool | 2–10x faster DB queries | Low |
| Gzip compression | 50–70% smaller transfers | Low |
| BBR TCP | 20–40% better throughput (high latency) | Very low |
| Swap + swappiness tuning | Prevents OOM crashes | Very low |
| Nginx worker tuning | Higher concurrent connection handling | Low |
Final Thoughts
The optimizations in this guide are not theoretical improvements — they’re the configurations used by production web infrastructure serving millions of requests daily. Nginx FastCGI caching, Redis, PHP OPcache, BBR TCP, and a properly tuned MariaDB buffer pool together transform a standard VPS into a high-performance server capable of handling serious traffic.
Start with the highest-impact changes first: FastCGI caching, Redis, OPcache, and the InnoDB buffer pool. These four changes alone typically deliver 5–10x performance improvements on WordPress and PHP sites. Then layer in the kernel tuning and swap configuration for maximum stability and throughput.
Remember: the best hardware upgrade is often a configuration upgrade. A well-tuned VPS.DO $20/month plan consistently outperforms a poorly configured $100/month plan. Apply these settings, measure the results, and you’ll see the difference.
Questions about your specific performance bottleneck? VPS.DO’s support team is available 24/7 →
Related articles you might find useful: