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:
| Metric | Before Redis | After Redis | Improvement |
| Database queries per page | 120-200 | 15-30 | 75-85% reduction |
| TTFB (Time to First Byte) | 1.2-2.5s | 0.3-0.6s | 60-75% faster |
| Page load time | 3.5-6.0s | 1.0-2.0s | 60-70% faster |
| Admin dashboard load | 4-8s | 1-2s | 75% faster |
| Memory per request | 45MB | 30MB | 33% 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
jpeg png webp avif gif ico svg css js woff2 woff ttf
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:
| Content | CDN Cache TTL | Purge Strategy |
| Product images | 30 days | Purge on product update |
| Theme CSS/JS | 1 year | Filename versioning |
| Category pages | 5 minutes | Purge on product add/remove |
| Homepage | 10 minutes | Purge on content change |
| Blog images | 30 days | Rarely purged |
| Fonts | 1 year | Never 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 Layer | Page Load Impact | Implementation Time | Complexity |
| Redis object cache | -60% TTFB | 30 minutes | Low |
| MySQL index tuning | -25% query time | 1 hour | Medium |
| OPcache | -40% PHP execution | 15 minutes | Low |
| FastCGI page cache | -90% for cached pages | 1 hour | Medium |
| CDN | -50% for static assets | 30 minutes | Low |
| Image optimization | -40% page weight | 2 hours | Medium |
| PHP 8.2+ upgrade | -15% overall | 2 hours | Medium |
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.