WooCommerce Performance on VPS: Redis Object Cache, Query Optimization, and Page Caching

Published 22 min read
wordpresswoocommercevps performanceredismysqlpage cache

WooCommerce on a properly tuned VPS loads product pages in under 800ms and handles 200+ concurrent shoppers on a $40/month server. The default WordPress + WooCommerce installation makes 50-200 database queries per page load, which is the primary bottleneck. This guide covers Redis object caching, MySQL query optimization, nginx FastCGI page caching, and CDN integration to reduce that to 5-10 queries and deliver sub-second page loads.

These optimizations apply to WooCommerce stores with 100 to 50,000 products. For server provisioning options, see the Pricing page.

Redis Object Cache Configuration

WordPress object cache stores database query results in memory. Without it, every page load runs the same database queries repeatedly: site options, active plugins, theme settings, menu structure, widget data, and user sessions. Redis object cache eliminates 60-80% of database queries.

Install and configure Redis for WordPress:

<h1>Install Redis server</h1>

sudo apt install redis-server

<h1>Configure Redis for WordPress (edit /etc/redis/redis.conf)</h1>

maxmemory 256mb

maxmemory-policy allkeys-lru

save ""

tcp-keepalive 300

timeout 0

<h1>Restart Redis</h1>

sudo systemctl restart redis-server

<h1>Install PHP Redis extension</h1>

sudo apt install php8.2-redis

sudo systemctl restart php8.2-fpm

Add the following to `wp-config.php`:

// Redis object cache configuration

define('WP_REDIS_HOST', '127.0.0.1');

define('WP_REDIS_PORT', 6379);

define('WP_REDIS_DATABASE', 0);

define('WP_REDIS_PREFIX', 'woo:');

define('WP_REDIS_MAXTTL', 86400); // 24 hours max TTL

// Disable file-based object cache

define('WP_CACHE', true);

// Increase memory limit for WooCommerce

define('WP_MEMORY_LIMIT', '256M');

define('WP_MAX_MEMORY_LIMIT', '512M');

Install the Redis Object Cache plugin and activate it. The plugin drops an `object-cache.php` file into `wp-content/` that intercepts all WordPress cache calls and routes them to Redis.

Impact measurement:

MetricBefore RedisAfter RedisImprovement

Database queries per page120-20015-3075-85% reduction
TTFB (Time to First Byte)1.2-2.5s0.3-0.6s60-75% faster
Page load time3.5-6.0s1.0-2.0s60-70% faster
Admin dashboard load4-8s1-2s75% faster
Memory per request45MB30MB33% reduction

MySQL Query Optimization for WooCommerce

WooCommerce stores product data across multiple tables: `wp_posts`, `wp_postmeta`, `wp_wc_product_meta_lookup`, `wp_wc_order_stats`, and taxonomy tables. The `wp_postmeta` table is the biggest performance bottleneck because WordPress uses it as a key-value store with no indexes on `meta_value`.

Add these indexes and configuration changes:

-- Add index for postmeta lookups (biggest WooCommerce improvement)

ALTER TABLE wp_postmeta ADD INDEX idx_meta_key_value (meta_key(191), meta_value(100));

-- Add index for product price filtering

ALTER TABLE wp_postmeta ADD INDEX idx_price_lookup (meta_key, meta_value)

WHERE meta_key IN ('_price', '_regular_price', '_sale_price');

-- Optimize WooCommerce order queries

ALTER TABLE wp_wc_order_stats ADD INDEX idx_order_date_status (date_created, status);

-- Clean up orphaned postmeta (reduces table size 10-30%)

DELETE pm FROM wp_postmeta pm

LEFT JOIN wp_posts p ON pm.post_id = p.ID

WHERE p.ID IS NULL;

-- Clean up transients (can accumulate to millions of rows)

DELETE FROM wp_options WHERE option_name LIKE '_transient_%';

DELETE FROM wp_options WHERE option_name LIKE '_site_transient_%';

-- Optimize autoloaded options (reduces memory per request)

UPDATE wp_options SET autoload = 'no'

WHERE option_name LIKE '%_transient_%'

OR option_name LIKE 'wc_report_%'

OR option_name LIKE '_wc_session_%';

MySQL server configuration for WooCommerce (`/etc/mysql/mysql.conf.d/mysqld.cnf`):

[mysqld]

<h1>InnoDB buffer pool (50-70% of available RAM)</h1>

innodb_buffer_pool_size = 2G

innodb_buffer_pool_instances = 4

<h1>Write performance</h1>

innodb_log_file_size = 256M

innodb_log_buffer_size = 64M

innodb_flush_log_at_trx_commit = 2

innodb_flush_method = O_DIRECT

<h1>Connection handling</h1>

max_connections = 150

thread_cache_size = 16

table_open_cache = 4000

table_definition_cache = 2000

<h1>Query cache (MySQL 5.7) or disable (MySQL 8.0+)</h1>

query_cache_type = 0

<h1>Temporary tables</h1>

tmp_table_size = 64M

max_heap_table_size = 64M

<h1>Slow query logging</h1>

slow_query_log = 1

slow_query_log_file = /var/log/mysql/slow.log

long_query_time = 1

The `innodb_flush_log_at_trx_commit = 2` setting provides a significant write performance boost (2-5x) at the cost of potentially losing the last second of transactions during a crash. For ecommerce, pair this with Redis-backed sessions so that cart data survives a MySQL restart.

Nginx FastCGI Page Cache

Page caching stores the fully rendered HTML output and serves it directly from nginx without executing PHP at all. This reduces response time from 500ms to 10-50ms for cached pages.

<h1>FastCGI cache configuration (add to http block)</h1>

fastcgi_cache_path /var/cache/nginx/woo levels=1:2

keys_zone=WOOCACHE:100m inactive=60m max_size=1g;

fastcgi_cache_key "$scheme$request_method$host$request_uri";

server {

listen 443 ssl http2;

server_name shop.example.com;

root /var/www/shop/public_html;

index index.php;

ssl_certificate /etc/letsencrypt/live/shop.example.com/fullchain.pem;

ssl_certificate_key /etc/letsencrypt/live/shop.example.com/privkey.pem;

# Cache bypass rules for WooCommerce

set $skip_cache 0;

# Skip cache for logged-in users

if ($http_cookie ~* "wordpress_logged_in") {

set $skip_cache 1;

}

# Skip cache for WooCommerce cart and checkout

if ($request_uri ~* "/cart/

/checkout//my-account/
/add-to-cart") {

set $skip_cache 1;

}

# Skip cache for WooCommerce cookies

if ($http_cookie ~* "woocommerce_items_in_cart|wp_woocommerce_session") {

set $skip_cache 1;

}

# Skip cache for POST requests

if ($request_method = POST) {

set $skip_cache 1;

}

# Static files

location ~* \.(jpg

jpegpngwebpavifgificosvgcssjswoff2woffttf
eot)$ {

expires 365d;

add_header Cache-Control "public, immutable";

access_log off;

log_not_found off;

}

# PHP with FastCGI cache

location ~ \.php$ {

fastcgi_pass unix:/run/php/php8.2-fpm.sock;

fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;

include fastcgi_params;

# Cache settings

fastcgi_cache WOOCACHE;

fastcgi_cache_valid 200 60m;

fastcgi_cache_valid 301 302 10m;

fastcgi_cache_valid 404 1m;

fastcgi_cache_bypass $skip_cache;

fastcgi_no_cache $skip_cache;

fastcgi_cache_use_stale error timeout updating;

# Cache status header (for debugging)

add_header X-Cache-Status $upstream_cache_status;

}

location / {

try_files $uri $uri/ /index.php?$args;

}

# Block xmlrpc.php (common attack vector)

location = /xmlrpc.php {

deny all;

return 444;

}

# Block wp-config.php access

location = /wp-config.php {

deny all;

}

}

The cache bypass rules are critical for WooCommerce. Cart pages, checkout, and logged-in user pages must never be cached because they contain user-specific data. The `$upstream_cache_status` header helps debug caching: `HIT` means served from cache, `MISS` means PHP was executed, `BYPASS` means the request matched a skip rule.

CDN Integration for WooCommerce

A CDN caches static assets at edge locations worldwide. For WooCommerce, product images are the biggest bandwidth consumer. A CDN reduces image load times from 500-2,000ms to 50-200ms for international customers.

CDN setup checklist:

  • Product images: Set `Cache-Control: public, max-age=2592000` (30 days). Product images rarely change.
  • Theme assets (CSS/JS): Set `Cache-Control: public, max-age=31536000, immutable`. Use versioned filenames.
  • Uploaded media: Set `Cache-Control: public, max-age=604800` (7 days).
  • Dynamic pages: Do not cache at CDN. Let nginx FastCGI cache handle these.
  • WooCommerce REST API: Do not cache. API responses contain user-specific data.
  • ContentCDN Cache TTLPurge Strategy

    Product images30 daysPurge on product update
    Theme CSS/JS1 yearFilename versioning
    Category pages5 minutesPurge on product add/remove
    Homepage10 minutesPurge on content change
    Blog images30 daysRarely purged
    Fonts1 yearNever purged

    Arxentra includes integrated CDN edge caching through its Cloud Computing platform, with a manual cache purge available per service from the dashboard.

    PHP-FPM Tuning for WordPress

    ; /etc/php/8.2/fpm/pool.d/wordpress.conf
    

    [wordpress]

    user = www-data

    group = www-data

    listen = /run/php/php8.2-fpm.sock

    pm = dynamic

    pm.max_children = 30

    pm.start_servers = 8

    pm.min_spare_servers = 4

    pm.max_spare_servers = 12

    pm.max_requests = 500

    php_admin_value[memory_limit] = 256M

    php_admin_value[max_execution_time] = 300

    php_admin_value[upload_max_filesize] = 64M

    php_admin_value[post_max_size] = 64M

    ; OPcache settings

    php_admin_value[opcache.enable] = 1

    php_admin_value[opcache.memory_consumption] = 256

    php_admin_value[opcache.max_accelerated_files] = 10000

    php_admin_value[opcache.validate_timestamps] = 0

    php_admin_value[opcache.revalidate_freq] = 0

    WordPress + WooCommerce uses more memory per request (80-120MB) than vanilla PHP applications because of the plugin architecture. The `pm.max_children = 30` is calculated for a 4GB RAM VPS after accounting for MySQL (2GB) and Redis (256MB).

    Image Optimization Pipeline

    Product images account for 60-80% of page weight on WooCommerce stores. An optimization pipeline reduces image size by 40-70% without visible quality loss:

    #!/bin/bash
    

    <h1>optimize-images.sh - Run after uploading new products</h1>

    UPLOAD_DIR="/var/www/shop/wp-content/uploads"

    <h1>Convert to WebP with 80% quality</h1>

    find "$UPLOAD_DIR" -name "*.jpg" -o -name "*.png" | while read img; do

    webp_file="${img%.*}.webp"

    if [ ! -f "$webp_file" ]; then

    cwebp -q 80 "$img" -o "$webp_file"

    fi

    done

    <h1>Optimize original JPEGs (lossless)</h1>

    find "$UPLOAD_DIR" -name "*.jpg" -exec jpegoptim --strip-all --max=85 {} \;

    <h1>Optimize PNGs (lossless)</h1>

    find "$UPLOAD_DIR" -name "*.png" -exec optipng -o2 {} \;

    echo "Image optimization complete"

    Serve WebP images with nginx using the `try_files` directive that falls back to the original format for browsers that do not support WebP.

    For additional WooCommerce hosting guides and managed solutions, explore the Services Overview page. See the Documentation for step-by-step VPS setup tutorials.

    Performance Comparison Table

    Optimization LayerPage Load ImpactImplementation TimeComplexity

    Redis object cache-60% TTFB30 minutesLow
    MySQL index tuning-25% query time1 hourMedium
    OPcache-40% PHP execution15 minutesLow
    FastCGI page cache-90% for cached pages1 hourMedium
    CDN-50% for static assets30 minutesLow
    Image optimization-40% page weight2 hoursMedium
    PHP 8.2+ upgrade-15% overall2 hoursMedium

    Frequently Asked Questions

    Q: How much RAM does WooCommerce need on a VPS?

    A: Minimum 4GB for stores under 5,000 products. Recommended 8GB for stores with 5,000-50,000 products and heavy traffic. The RAM allocation should give MySQL 50% of available memory, PHP-FPM 30%, Redis 5%, and the OS 15%.

    Q: Should I use MySQL or MariaDB for WooCommerce?

    A: Both work well. MariaDB 10.6+ offers slightly better query optimizer performance for WooCommerce workloads and includes the Aria storage engine for temporary tables. MySQL 8.0+ has better JSON support and window functions. Either choice delivers similar performance with proper tuning.

    Q: How do I handle WooCommerce during traffic spikes (sales events)?

    A: Pre-warm the nginx FastCGI cache by crawling all product and category pages before the sale starts. Temporarily increase PHP-FPM `pm.max_children` by 50%. Enable Redis queue for order processing so checkout remains fast while orders process in the background.

    Q: Is page caching safe for WooCommerce stores?

    A: Yes, with proper bypass rules. Cart, checkout, my-account, and any page with user-specific content must bypass the cache. The nginx configuration above handles all WooCommerce-specific cache bypass rules. Test by adding items to cart as a guest and verifying the cart page shows correct items.

    Q: How often should I optimize the MySQL database?

    A: Run `OPTIMIZE TABLE` on `wp_postmeta` and `wp_options` monthly. Clean up expired transients weekly. Monitor the slow query log daily during the first month, then weekly. The cleanup SQL commands above can be scheduled as a weekly cron job.

    Q: Can I run multiple WordPress sites on one VPS?

    A: Yes. Create separate PHP-FPM pools, nginx server blocks, and MySQL databases for each site. With 8GB RAM, a single VPS comfortably handles 3-5 low-traffic WordPress sites or 2 high-traffic WooCommerce stores. Use separate Redis databases (db 0, 1, 2, etc.) to prevent cache key collisions. Contact us via the Contact page for multi-site architecture planning.