A slow VPS wastes every other optimization you made. You can minify JavaScript and compress images all day, but if your server takes three seconds to serve the first byte, visitors leave. The good news? Most VPS performance bottlenecks live in a handful of config files and system settings you can fix in an afternoon.
I've walked dozens of clients through these changes in support tickets. The pattern is always the same: someone upgraded their VPS RAM but saw no speed gain, or they migrated to a new provider and performance actually got worse. The hardware wasn't the problem—the default operating-system and web-server settings were.
Below are eight changes that produce measurable improvements in response time, throughput, and resource utilization. You won't need to rewrite application code or buy expensive monitoring software. Just SSH access and a text editor.
1. Raise kernel connection limits
Linux imposes conservative defaults on how many network connections and open files a process can handle. On a busy web server those limits become a ceiling fast.
Edit /etc/sysctl.conf and add:
net.core.somaxconn = 65535
net.ipv4.ip_local_port_range = 1024 65535
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 15
fs.file-max = 200000
Then apply the changes:
sudo sysctl -p
The somaxconn value controls the queue depth for incoming TCP connections. Default is often 128, which sounds like a lot until you realize Nginx or Apache can accept thousands of connections per second under load. The tcp_tw_reuse flag lets the kernel recycle sockets in TIME_WAIT state faster, freeing up ports for new connections. tcp_fin_timeout shortens how long closed sockets linger.
You also need per-process limits. Edit /etc/security/limits.conf and append:
* soft nofile 100000
* hard nofile 100000
Log out and back in for shell sessions, or reboot. Check the new limit with ulimit -n.
In my experience, hitting file-descriptor limits is the number-one cause of cryptic 502 errors when traffic spikes. Bump these values early.
2. Switch to a modern web server or tune the one you have
Apache with prefork MPM is still the default on many shared-hosting control panels. It's stable and battle-tested, but it spawns a full process per connection. That eats RAM fast.
If you're on Apache, switch to the event MPM:
sudo a2dismod mpm_prefork
sudo a2enmod mpm_event
sudo systemctl restart apache2
Then edit /etc/apache2/mods-available/mpm_event.conf:
<IfModule mpm_event_module>
StartServers 4
MinSpareThreads 25
MaxSpareThreads 75
ThreadLimit 64
ThreadsPerChild 25
MaxRequestWorkers 400
MaxConnectionsPerChild 10000
</IfModule>
Those numbers assume a 2–4 GB VPS. Scale MaxRequestWorkers based on available RAM: each worker thread uses much less memory than a prefork process, but you still don't want to exceed physical RAM.
Better yet, put Nginx in front of Apache as a reverse proxy, or replace Apache entirely. Nginx handles static files and keeps connections open with minimal memory, then proxies dynamic requests to a PHP-FPM or application backend. I've seen setups cut memory use by half just by moving static assets to Nginx.
3. Configure PHP-FPM pools correctly
If you run PHP, you're probably using PHP-FPM. Default pool settings are often wildly inappropriate for production traffic.
Edit your pool config (usually /etc/php/8.x/fpm/pool.d/www.conf or similar):
pm = dynamic
pm.max_children = 50
pm.start_servers = 5
pm.min_spare_servers = 5
pm.max_spare_servers = 10
pm.max_requests = 500
The max_children value is the hard cap on concurrent PHP processes. Set it too low and requests queue; set it too high and you run out of RAM. A rough formula: take your available RAM, subtract what the OS and web server need, then divide by the average PHP process size (often 30–50 MB). For a 4 GB VPS with 1 GB reserved for the OS and web server, that leaves 3 GB. At 50 MB per process, max_children = 60 is a safe ceiling.
The max_requests directive makes each worker restart after handling that many requests, which prevents memory leaks in poorly written PHP code from accumulating.
Restart PHP-FPM after changes:
sudo systemctl restart php8.2-fpm
4. Enable opcode caching
PHP compiles scripts into opcodes every time a request hits, unless you cache those opcodes. OPcache is built into modern PHP and makes a huge difference.
Edit your php.ini (location varies; try /etc/php/8.x/fpm/php.ini):
opcache.enable=1
opcache.memory_consumption=128
opcache.interned_strings_buffer=8
opcache.max_accelerated_files=10000
opcache.revalidate_freq=60
The memory_consumption value is how much RAM OPcache can use. For most WordPress or PHP applications, 128 MB is plenty. max_accelerated_files should exceed the number of PHP files in your codebase; you can count them with:
find /var/www -type f -name '*.php' | wc -l
Restart PHP-FPM, then verify OPcache is active by checking phpinfo() or running:
php -v
You should see "with Zend OPcache" in the output.
5. Add an object cache for database queries
Every dynamic page hits the database multiple times. WordPress, for example, runs dozens of queries per page load. Caching those results in memory cuts database load and speeds up responses.
Install Redis or Memcached. Redis is more versatile:
sudo apt install redis-server
sudo systemctl enable redis-server
sudo systemctl start redis-server
Then install the PHP extension:
sudo apt install php-redis
sudo systemctl restart php8.2-fpm
For WordPress, install the Redis Object Cache plugin and add this to wp-config.php:
define('WP_REDIS_HOST', '127.0.0.1');
define('WP_REDIS_PORT', 6379);
Activate the plugin and enable the cache from its settings page. You'll see query counts drop by half or more.
For custom applications, use a library like Predis or PhpRedis to cache expensive queries, API responses, or rendered HTML fragments.
6. Compress responses with Brotli or Gzip
Text content—HTML, CSS, JavaScript, JSON—compresses dramatically. Sending compressed responses saves bandwidth and reduces transfer time, especially on slower connections.
Nginx supports both Gzip and Brotli. Brotli offers better compression but requires a separate module. For Gzip, add to your nginx.conf or a site config:
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml text/javascript application/json application/javascript application/xml+rss application/rss+xml font/truetype font/opentype application/vnd.ms-fontobject image/svg+xml;
Compression level 6 is a good balance between CPU cost and size reduction. Levels above 6 give diminishing returns.
For Apache, enable mod_deflate:
sudo a2enmod deflate
sudo systemctl restart apache2
And add to your virtual host or .htaccess:
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css text/javascript application/javascript application/json
</IfModule>
Verify compression is working by checking response headers:
curl -I -H "Accept-Encoding: gzip" https://yoursite.com
You should see Content-Encoding: gzip in the output.
7. Monitor resource usage actively
You can't optimize what you don't measure. Install lightweight monitoring so you know when CPU, RAM, or disk I/O becomes a bottleneck.
Start with htop for real-time process monitoring:
sudo apt install htop
htop
Press F5 to toggle tree view and see which processes consume the most resources. Sort by memory with F6 and select MEM%.
For persistent logging, install sysstat:
sudo apt install sysstat
sudo systemctl enable sysstat
sudo systemctl start sysstat
Then review historical CPU and I/O stats with:
sar -u 1 10
sar -d 1 10
The first command shows CPU utilization over ten one-second intervals; the second shows disk I/O.
Check disk space constantly. A full root partition will lock up your VPS fast:
df -h
I've seen more VPS slowdowns caused by a full disk than by CPU limits. Log rotation is often the culprit—check /var/log and configure logrotate to compress and delete old logs.
8. Offload static assets to a CDN or separate domain
Browsers limit how many concurrent connections they open to a single domain. Serving images, CSS, and JavaScript from a CDN or a cookieless subdomain like static.yoursite.com lets the browser download more files in parallel.
You don't need an expensive CDN. Cloudflare's free tier caches static assets globally and is trivial to set up: point your DNS to Cloudflare, enable the orange cloud, and let Cloudflare's edge servers handle the traffic.
If you want finer control, configure Nginx to serve static files directly and set long cache headers:
location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff2)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
That tells browsers to cache those files for a year. When you update a file, change its filename or append a version query string (style.css?v=2).
What combination works best?
Start with kernel limits and web-server tuning—those changes are universal and safe. Then add opcode caching and an object cache if you run PHP or a database-heavy application. Compression and CDN offloading come next because they reduce bandwidth without changing server behavior.
Monitoring should be running from day one so you can see which change made a difference. In tickets I handled, clients often thought they needed more CPU when the real problem was a misconfigured PHP-FPM pool spawning too many workers and swapping to disk.
Test each change under realistic load. Use a tool like Apache Bench or wrk to simulate concurrent requests:
ab -n 1000 -c 10 https://yoursite.com/
That sends 1,000 requests with 10 concurrent connections. Compare response times before and after each tweak.
How much faster will my site get?
That depends on your current bottleneck. If you're hitting file-descriptor limits, fixing that can cut 502 errors to zero and drop response times by half. If your database is the bottleneck, adding Redis might shave a second off page loads. Compression and CDN offloading typically save 20–40% of transfer time.
You won't see dramatic improvements if your application code is inefficient—no amount of server tuning fixes a slow database query that returns ten thousand rows. Profile your application first.
Do I need to reboot after changing sysctl settings?
No. Run sysctl -p and the kernel picks up the new values immediately. Per-process limits in /etc/security/limits.conf require you to log out and back in, or restart the affected service.
Will these tweaks work on a 1 GB VPS?
Most will, but scale the numbers down. Set pm.max_children to 20 or 25 instead of 50, and keep opcache.memory_consumption at 64 MB. A 1 GB VPS can't run as many concurrent processes, so you'll hit limits sooner under heavy traffic.
Can I use Brotli with Apache?
Yes, but it requires compiling mod_brotli from source on most distributions. Gzip is easier and offers nearly the same benefit. If you're chasing every last byte of compression, switch to Nginx—it has better Brotli support.
Where to focus first
Check your current limits and usage before changing anything. Run ulimit -n, free -h, and htop to see what's actually constrained. Look at your web-server error logs for clues like "Too many open files" or "Cannot allocate memory."
Then apply the changes that match your bottleneck. Kernel limits and PHP-FPM tuning are safe bets for almost every VPS. Opcode caching is mandatory if you run PHP. Object caching and compression come next, followed by CDN offloading once the server itself is optimized.
Monitor everything so you know what worked. Every VPS and workload is different, and the only way to confirm a change helped is to measure response times and resource usage before and after.
