Speed Up WordPress on VPS Using PHP-FPM, OPcache and Redis
A well-configured WordPress installation on VPS can load within 200–500ms, but if misconfigured or not optimized at all, it may take 3–5 seconds. This guide covers every layer you should tune.
Layer 1: PHP-FPM Instead of mod_php
If you're still running Apache with mod_php, switch to PHP-FPM. PHP-FPM runs PHP as a separate pool independent from Apache, reducing memory footprint and providing better support for concurrent requests.
apt install php8.3-fpm
# Enable modules in Apache
a2enmod proxy_fcgi setenvif
a2enconf php8.3-fpm
systemctl restart apache2
Layer 2: OPcache Reduces PHP Recompilation
Every time PHP runs a script, it must parse and compile it first. OPcache stores the compiled bytecode in RAM, eliminating the need to recompile on every request.
Add to /etc/php/8.3/fpm/php.ini:
opcache.enable=1
opcache.memory_consumption=256
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=10000
opcache.revalidate_freq=0
opcache.validate_timestamps=0 # Disable for maximum performance (must clear cache on deploy)
Layer 3: Redis Object Cache
WordPress queries the database on every request, even when data doesn't change. Redis caches database query results in RAM, drastically reducing database calls.
apt install redis-server php-redis
systemctl enable redis-server
Install the Redis Object Cache plugin and add to wp-config.php:
define('WP_REDIS_HOST', '127.0.0.1');
define('WP_REDIS_PORT', 6379);
define('WP_CACHE', true);
Layer 4: Nginx FastCGI Cache
For unauthenticated visitors, Nginx can serve cached HTML directly without running PHP at all—the fastest approach.
Add to nginx.conf:
fastcgi_cache_path /tmp/nginx-cache levels=1:2 keys_zone=WORDPRESS:100m inactive=60m;
fastcgi_cache_key "$scheme$request_method$host$request_uri";
In your server block:
location ~ \.php$ {
fastcgi_cache WORDPRESS;
fastcgi_cache_valid 200 60m;
fastcgi_cache_bypass $skip_cache;
fastcgi_no_cache $skip_cache;
add_header X-Cache $upstream_cache_status;
# ... standard fastcgi_pass config follows
}
Layer 5: Tune PHP-FPM Pool
In /etc/php/8.3/fpm/pool.d/www.conf:
pm = dynamic
pm.max_children = 20 # RAM / 30MB per process
pm.start_servers = 4
pm.min_spare_servers = 2
pm.max_spare_servers = 6
pm.max_requests = 500 # Restart process every 500 requests to prevent memory leaks
What Helps Less Than You'd Think
- Plugin cache like WP Super Cache helps, but less than Nginx-level caching
- CDN helps with static file delivery but doesn't improve PHP TTFB
- Reducing plugins helps, but the impact is minimal if core PHP is still slow
Verify Cache is Working Correctly
# Check OPcache status
php -r "print_r(opcache_get_status());" | grep hits
# Check Redis hit rate
redis-cli info stats | grep "keyspace_hits\|keyspace_misses"
# Check Nginx FastCGI cache header
curl -I https://example.com/ | grep X-Cache
# Should see X-Cache: HIT starting from the 2nd request onward
$skip_cache variable in Nginx config to bypass FastCGI cache for requests with WordPress login cookies or ?nocache=1 parameterFastCGI Cache Skip Rules for WordPress
Add to your Nginx config before the server block to bypass cache when user is logged in or in the admin area:
map $http_cookie $skip_cache {
default 0;
"~*wordpress_logged_in" 1;
"~*wp-postpass_" 1;
"~*comment_author_" 1;
}
map $request_method $skip_cache_method {
POST 1;
default 0;
}
# In server block
set $skip_cache 0;
if ($request_method = POST) { set $skip_cache 1; }
if ($query_string != "") { set $skip_cache 1; }
if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_no_cache|wordpress_logged_in") {
set $skip_cache 1;
}
Common Issues & Troubleshooting
WordPress Shows 500 Error After Enabling OPcache
Often caused by validate_timestamps=0, preventing WordPress from seeing recently modified files. Clear the cache first:
# Clear OPcache via PHP script
php -r "opcache_reset();"
# Or restart PHP-FPM
systemctl restart php8.3-fpm
Redis Object Cache Plugin Says "Object cache could not be connected"
# Check if Redis is running
systemctl status redis-server
redis-cli ping # Should return PONG
# Check if PHP has the redis extension
php -m | grep redis
# If not, install it
apt install php8.3-redis
systemctl restart php8.3-fpm
Nginx FastCGI Cache Works, But WordPress Admin Breaks or Shows Old Content
Verify that $skip_cache rules are working correctly:
# Add a temporary header for debugging
add_header X-Cache-Skip $skip_cache always;
# Then curl and check
curl -I -b "wordpress_logged_in_xxx=yyy" https://example.com/ | grep X-Cache
PHP-FPM Uses Too Much RAM and Grows Without Stopping
Set pm.max_requests to restart worker processes after handling a set number of requests, preventing accumulated memory leaks:
pm.max_requests = 500
# Workers will automatically restart every 500 requests
OPcache Hit Rate Below 90%
Increase opcache.max_accelerated_files to cover all PHP files in your project:
# Count the number of PHP files
find /var/www/html -name "*.php" | wc -l
# Then set max_accelerated_files higher than that number
opcache.max_accelerated_files=20000
Frequently Asked Questions
How do OPcache and Redis Object Cache differ?
OPcache stores compiled PHP bytecode, reducing parse/compile time per request. Redis Object Cache stores database query results, reducing how often WordPress communicates with MySQL. They operate at different layers and work well together—enable both simultaneously.
For a 2GB RAM VPS with WordPress, what should php-fpm pm.max_children be?
Use this formula: (Available RAM after OS/MySQL) ÷ RAM per PHP worker (typically 25–35 MB). For 2GB with MySQL using ~512MB, you have ~1.5GB left for PHP → max_children = 40–50. Verify actual per-worker memory with ps aux | grep php-fpm | awk '{print $6}'
Will Nginx FastCGI Cache cause issues with WordPress admin?
No, if you configure skip_cache rules correctly. Requests with WordPress login cookies automatically bypass cache, so admins always see current content, not stale cache. Don't forget to set rules for WooCommerce cart and checkout if you're using them.
Where can I measure WordPress speed for free?
Use Google PageSpeed Insights (pagespeed.web.dev) for real Core Web Vitals, GTmetrix for waterfall charts showing slow resources, or WebPageTest.org for multi-location testing. Focus on TTFB (Time to First Byte), which should be under 200ms after proper caching setup.
Summary
The order of impact is: OPcache first → Redis Object Cache → Nginx FastCGI Cache. Spending just a few hours on these steps can reduce server response time by 80–90%. Always verify results using X-Cache headers and TTFB before and after.