Nginx, PHP-FPM, and Redis Tuning That Actually Speeds Up a VPS

Most “my VPS is slow” reports end with the same root cause: not a weak CPU or saturated network, but a web stack configured with defaults that assume more memory than the box has. Nginx, PHP-FPM, and Redis each have a handful of settings that decide whether a 2 GB VPS serves a busy site comfortably or swaps itself into a crawl. Before sizing anything, sanity-check the host itself against the plans on our VPS comparison table — tuning cannot manufacture RAM that was never allocated.

Measure First: Where Is the Time Going?

Capture a baseline before changing anything, so you can prove the effect of each tweak:

# Total request time, plus a breakdown
curl -o /dev/null -s -w 'connect: %{time_connect}s\n'
     -w 'ttfb:    %{time_starttransfer}s\n'
     -w 'total:   %{time_total}s\n' https://your-vps.example/

# Where memory actually goes
free -h
ps aux --sort=-%mem | head -12

If TTFB is high but the page itself is small, the delay is almost always PHP-FPM waiting for a free worker, or PHP waiting on a slow query — not Nginx. If TTFB is fine but total time is high, look at assets and caching next. Tune the layer the numbers point at.

PHP-FPM: Size the Pool with Memory Math, Not Guesses

The single most impactful PHP-FPM setting is pm.max_children. The default pm = dynamic with max_children = 50 assumes far more memory than a small VPS has. Do the arithmetic:

# Average RSS of one PHP-FPM worker under load
ps -o rss= -C php-fpm8.2 | awk '{s+=$1; n++} END {printf "avg RSS: %.0f MB (%d workers)\n", s/n/1024, n}'



Suppose workers average 80 MB and your box has 2 GB total with Nginx, MySQL, and Redis claiming roughly 800 MB. That leaves about 1.2 GB for PHP, so pm.max_children = 15 (15 × 80 MB = 1.2 GB) is the honest ceiling — not 50. Set pm = ondemand for low-traffic sites so idle workers are freed instead of parked, and enable the slow log to find the scripts that hold workers hostage:

pm = ondemand
pm.max_children = 15
pm.process_idle_timeout = 10s
slowlog = /var/log/php-fpm-slow.log
request_slowlog_timeout = 5s

Watch tail -f /var/log/php-fpm-slow.log for a week. If the same function appears repeatedly, fix the query or cache its result — raising max_children to paper over a slow script just moves the problem to the OOM killer.

Nginx: Small Changes, Measurable Gains

  • worker_processes auto; — one worker per vCPU; more workers than cores only add context-switch overhead on a shared host.
  • worker_connections 1024; — 1024 connections per worker is plenty for a small VPS; raising it without raising worker_rlimit_nofile does nothing.
  • keepalive 65; and keepalive_requests 100; — reuse upstream connections instead of renegotiating TLS per request.
  • gzip on; gzip_comp_level 5; gzip_types text/css application/javascript application/json; — compressing CSS/JS/JSON is the cheapest bandwidth win available.

If PHP-FPM is on the same box, make sure fastcgi_pass uses a Unix socket rather than TCP (unix:/run/php/php8.2-fpm.sock), which avoids loopback overhead and one more thing for a firewall to misconfigure.

Redis: Cache the Right Layer

Redis earns its 50–100 MB of RAM when it caches the expensive layer — PHP object cache (WordPress, Magento, Laravel), session storage, or rate-limit counters. Pointless when it only caches what the page cache already handles. Verify it is actually being used:

redis-cli info stats | grep -E 'keyspace_hits|keyspace_misses'
redis-cli info memory | grep used_memory_human



A hit ratio below 80% means either the cache is being flushed constantly or the application is not really using it. Cap memory with maxmemory 256mb and maxmemory-policy allkeys-lru so Redis never becomes the thing that OOMs your PHP-FPM workers.

Re-Measure and Lock It In

Run the same curl timing from the beginning. On a typical WordPress site the combination of a correctly sized FPM pool, Nginx micro-optimizations, and a warm Redis object cache cuts TTFB from 400–600 ms to 80–150 ms. If you also enable a full-page cache for anonymous visitors, total time drops into the tens of milliseconds — at which point the bottleneck moves to your connection, not the server.

The pattern is the same on every VPS: size workers to real memory, cache the layer that is actually expensive, and re-measure after every change. If your current plan cannot fit Nginx, PHP-FPM, and a database without swap churn, the memory and storage comparison on our site helps you find a tier that can, and the provider ranking lists hosts where these settings translate into real throughput rather than burstable marketing numbers.

Leave a Reply