WordPress VPS Performance Tuning: OPcache, Redis, HTTP/2, and Core Web Vitals Checklist

WordPress VPS Performance Tuning: OPcache, Redis, HTTP/2, and Core Web Vitals Checklist

WordPress performance on a VPS is not just about raw server speed — it is about systematically eliminating bottlenecks at each layer of the request path. A page load involves DNS resolution, TCP connection, TLS handshake, Nginx processing, PHP-FPM execution, MySQL queries, template rendering, and asset delivery. Optimizing each layer compounds: fixing four 25% improvements produces a dramatically faster site than fixing one 100% bottleneck. This guide walks through WordPress performance optimization on a VPS from the server layer down to Core Web Vitals, with actionable configuration for each component.

Measuring Before Optimizing

Establish a baseline before making changes so you can measure the impact of each optimization:

  • Google PageSpeed Insights (pagespeed.web.dev): Measures Core Web Vitals from real user data and provides specific recommendations
  • GTmetrix: Detailed waterfall analysis showing load time of each asset
  • WebPageTest: Multi-location testing with advanced metrics including Time to First Byte (TTFB)
  • Apache Bench: Server-side throughput measurement
# Baseline throughput test (run before and after optimization)
ab -n 500 -c 20 https://yourdomain.com/
# Note: requests/second and average response time

Layer 1: PHP OPcache Configuration

OPcache caches compiled PHP bytecode in memory, eliminating the compilation step on every request. This is the single highest-impact WordPress optimization available — it typically reduces PHP execution time by 50–70%.

sudo nano /etc/php/8.2/fpm/conf.d/10-opcache.ini
opcache.enable=1
opcache.enable_cli=1

; Memory: increase based on your theme/plugin count
; Run: php -r "echo opcache_get_status()['memory_usage']['used_memory']/(1024*1024),' MB';"
opcache.memory_consumption=256

; String interning buffer
opcache.interned_strings_buffer=16

; Maximum files to cache (count your PHP files: find /var/www -name "*.php" | wc -l)
opcache.max_accelerated_files=20000

; In production: disable file change checking (manual reset on deploy)
opcache.validate_timestamps=0
opcache.revalidate_freq=0

; Enable JIT compilation (PHP 8.0+)
opcache.jit_buffer_size=128M
opcache.jit=tracing

; Recommended additional settings
opcache.save_comments=1
opcache.fast_shutdown=1
opcache.max_wasted_percentage=5

Reset OPcache after deploying code changes or WordPress updates:

# Via WP-CLI
wp cache flush

# Or via PHP function (add to deployment script)
curl -s https://yourdomain.com/opcache-reset.php
sudo systemctl restart php8.2-fpm

Layer 2: PHP-FPM Worker Pool Tuning

Each PHP-FPM worker handles one request at a time. Too few workers means requests queue; too many wastes RAM. The correct number depends on how much RAM each WordPress process uses:

# Measure average PHP-FPM process size
ps --no-headers -o rss -C php-fpm8.2 | awk '{sum += $1} END {print sum/NR/1024 " MB average"}'
sudo nano /etc/php/8.2/fpm/pool.d/www.conf
; Formula: max_children = (available_RAM) / (average_process_size)
; For 4GB RAM server, ~500MB for OS/Nginx/MySQL, avg process 80MB:
; (3500MB - 256MB OPcache) / 80MB ≈ 40 children
pm = dynamic
pm.max_children = 40
pm.start_servers = 8
pm.min_spare_servers = 4
pm.max_spare_servers = 12
pm.max_requests = 500

; Status page for monitoring
pm.status_path = /php-fpm-status

Layer 3: Redis Object Caching

WordPress executes hundreds of database queries per page load without object caching. Redis stores the results of expensive database calls in memory, serving them from cache on subsequent requests.

sudo apt install redis-server -y
sudo systemctl enable redis-server

# Configure Redis for caching
sudo nano /etc/redis/redis.conf
maxmemory 256mb
maxmemory-policy allkeys-lru
save ""
appendonly no

In WordPress, install the Redis Object Cache plugin (by Till Krüss) and configure in wp-config.php:

define('WP_REDIS_HOST', '127.0.0.1');
define('WP_REDIS_PORT', 6379);
define('WP_REDIS_TIMEOUT', 1);
define('WP_REDIS_READ_TIMEOUT', 1);
define('WP_REDIS_DATABASE', 0);
define('WP_CACHE_KEY_SALT', 'yourdomain_');

Activate the plugin and enable object cache from the WordPress admin or WP-CLI:

wp redis enable

Verify caching is working:

redis-cli info stats | grep keyspace_hits
# Hit rate = hits / (hits + misses) * 100; aim for >85%

Layer 4: Nginx Microcaching

Nginx microcaching stores full-page HTML responses for 10–30 seconds. During traffic spikes, thousands of visitors requesting the same page receive the cached version — only one PHP-FPM worker is needed per cache lifetime period instead of one per visitor.

sudo nano /etc/nginx/nginx.conf

Add to the http block:

fastcgi_cache_path /var/cache/nginx/wordpress
  levels=1:2
  keys_zone=WORDPRESS:100m
  inactive=60m
  max_size=1g;
fastcgi_cache_key "$scheme$request_method$host$request_uri$cookie_woocommerce_items_in_cart";

In your WordPress server block:

set $skip_cache 0;

# Skip cache for logged-in users, POST requests, and WooCommerce cart
if ($request_method = POST) { set $skip_cache 1; }
if ($query_string != "") { set $skip_cache 1; }
if ($request_uri ~* "/wp-admin/|/wp-login.php|/cart/|/checkout/|/my-account/") {
    set $skip_cache 1;
}
if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp-postpass|woocommerce_items_in_cart|wordpress_logged_in") {
    set $skip_cache 1;
}

location ~ \.php$ {
    fastcgi_cache WORDPRESS;
    fastcgi_cache_valid 200 30s;
    fastcgi_cache_use_stale error timeout invalid_header updating;
    fastcgi_cache_lock on;
    fastcgi_cache_bypass $skip_cache;
    fastcgi_no_cache $skip_cache;

    add_header X-FastCGI-Cache $upstream_cache_status;

    # ... your existing fastcgi_pass configuration
}
sudo mkdir -p /var/cache/nginx/wordpress
sudo chown www-data:www-data /var/cache/nginx/wordpress
sudo nginx -t && sudo systemctl reload nginx

Layer 5: HTTP/2 and TLS Optimization

HTTP/2 multiplexes multiple requests over a single TCP connection, dramatically reducing the overhead of loading many assets. It requires HTTPS — configure it in your Nginx listener:

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;

    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers on;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 1d;
    ssl_session_tickets off;

    # OCSP Stapling (reduces TLS handshake time)
    ssl_stapling on;
    ssl_stapling_verify on;
    resolver 1.1.1.1 8.8.8.8 valid=300s;

    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
}

Layer 6: Database Query Optimization

Enable MySQL Slow Query Log

sudo nano /etc/mysql/mariadb.conf.d/50-server.cnf
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow-queries.log
long_query_time = 1
log_queries_not_using_indexes = 1
sudo systemctl restart mariadb
# After some traffic, analyze slow queries:
sudo mysqldumpslow -t 20 /var/log/mysql/slow-queries.log

WordPress Database Optimization

# Remove post revisions (major source of table bloat)
wp post delete $(wp post list --post_type='revision' --format=ids) --force

# Clean expired transients
wp transient delete --expired

# Optimize tables
wp db optimize

# Add missing indexes (WP 6.4+ improved this)
wp cron event run wp_scheduled_auto_draft_delete

Layer 7: Asset Optimization for Core Web Vitals

Core Web Vitals Targets (2025)

Metric Good Needs Improvement Poor
LCP (Largest Contentful Paint) <2.5s 2.5s–4.0s >4.0s
INP (Interaction to Next Paint) <200ms 200ms–500ms >500ms
CLS (Cumulative Layout Shift) <0.1 0.1–0.25 >0.25
TTFB (Time to First Byte) <800ms 800ms–1800ms >1800ms

Nginx Gzip and Static Asset Optimization

gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_min_length 256;
gzip_types
  application/javascript
  application/json
  application/xml
  image/svg+xml
  text/css
  text/javascript
  text/plain
  text/xml;

# Long-term caching for versioned assets
location ~* \.(css|js|woff2|png|jpg|jpeg|gif|ico|svg|webp)$ {
    expires 1y;
    add_header Cache-Control "public, immutable";
    add_header Vary "Accept-Encoding";
    access_log off;
}

Image Optimization

Images are typically the largest contributors to page weight. On your VPS:

# Install image optimization tools
sudo apt install webp imagemagick -y

# Convert existing JPEGs to WebP (run in wp-content/uploads)
find /var/www/yourdomain/wp-content/uploads -name "*.jpg" -exec \
  cwebp -q 82 {} -o {}.webp \;

Use a WordPress plugin (Imagify, ShortPixel, or the free EWWW Image Optimizer) for automatic optimization of new uploads, or implement Nginx content negotiation to serve WebP to supporting browsers.

Complete Performance Optimization Checklist

  • ☐ PHP OPcache enabled with validate_timestamps=0 in production
  • ☐ OPcache JIT enabled (PHP 8.0+)
  • ☐ PHP-FPM pool sized to available RAM
  • ☐ Redis object cache active and hit rate above 85%
  • ☐ Nginx microcaching enabled with correct exclusions for logged-in users and WooCommerce
  • ☐ HTTP/2 enabled on all HTTPS listeners
  • ☐ TLS session caching and OCSP stapling configured
  • ☐ Gzip compression enabled for text assets
  • ☐ Static assets served with 1-year cache headers
  • ☐ MariaDB slow query log analyzed and queries optimized
  • ☐ Unused WordPress plugins deactivated
  • ☐ Images served in WebP format with appropriate fallbacks
  • ☐ CDN configured for static asset delivery
  • ☐ Core Web Vitals scores measured and documented

Getting Started

A WordPress site with full optimization — OPcache, Redis, Nginx microcaching, HTTP/2, and asset optimization — can handle 10–50x more traffic on the same hardware compared to an unoptimized setup. Start with a Ubuntu VPS from VPS.DO with NVMe storage (critical for OPcache and Redis performance), and work through this checklist systematically. Measure TTFB and Core Web Vitals before and after each layer of optimization to understand the impact.

Conclusion

WordPress performance optimization on a VPS is a compound effort across multiple layers: PHP OPcache eliminates compilation overhead, Redis eliminates redundant database queries, Nginx microcaching serves cached pages to traffic spikes, HTTP/2 reduces connection overhead, and asset optimization reduces browser-side load time. Each layer is independently valuable, but the combination delivers dramatically better performance than any single optimization — and better Core Web Vitals scores that directly benefit search engine rankings.

Fast • Reliable • Affordable VPS - DO It Now!

Get top VPS hosting with VPS.DO’s fast, low-cost plans. Try risk-free with our 7-day no-questions-asked refund and start today!