WOOCOMMERCE
July 2, 2026

How to Host a Fast WooCommerce Store on a VPS

9 min read
Author
CloudStick Team
WordPress Engineer
Share this article
How to Host a Fast WooCommerce Store on a VPS
CloudStick
Speed Guide

Right-Sizing the VPS

A WooCommerce store needs more headroom than a static WordPress blog because every product page, cart update, and checkout step runs PHP against the database — there is no cacheable HTML for logged-in shopping sessions. For a catalog under 2,000 products with moderate traffic, start with 4 vCPUs and 8GB RAM on NVMe storage; that is enough to run Nginx, PHP-FPM, MariaDB, and Redis on one box without them fighting for memory.

CPU matters more than most hosts admit. Checkout and cart-fragment requests are AJAX calls (?wc-ajax=) that skip page cache entirely and hit PHP on every request, so a burst of concurrent shoppers is a CPU problem before it is a RAM problem. If you are running WooCommerce alongside other sites, isolate it — CloudStick gives WooCommerce its own PHP-FPM pool per site (config lives at fpm-pools.d/<site>.conf), so a traffic spike on the store never starves PHP workers on a neighboring site.

Set WP_MEMORY_LIMIT and PHP's memory_limit to at least 256M once you cross a few dozen plugins and extensions — WooCommerce's own memory footprint on product edit screens and bulk order exports is significant, and a hard limit here shows up as white screens at the worst possible moment, mid-checkout.

PHP-FPM Tuning

pm.max_children is the single most important number in your stack because it caps how many PHP requests run at once — set it too low and shoppers queue behind each other during a sale; set it too high and PHP-FPM exhausts RAM and the kernel OOM-kills MariaDB. Calculate it from available memory divided by average PHP process size (usually 40-80MB for WooCommerce with plugins): on an 8GB box reserving 3GB for MariaDB and Redis, roughly 60-70 children is a safe starting point with pm = dynamic.

OPcache eliminates the cost of recompiling PHP on every request, which matters enormously for WooCommerce's larger codebase. Enable it with enough shared memory and a high enough file count that the entire plugin stack fits without eviction — undersized OPcache thrashes and you lose most of the benefit.

; /etc/php83cs/php-fpm.d/opcache.ini
opcache.enable=1
opcache.memory_consumption=256
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=20000
opcache.validate_timestamps=1
opcache.revalidate_freq=60
opcache.save_comments=1

Keep opcache.validate_timestamps on for stores that get frequent plugin updates; drop it to 0 with a deploy-time opcache_reset() only once your release process is disciplined enough to handle stale cache risk.

Redis Object Cache

Redis object caching is the correct caching layer for WooCommerce because it caches database query results, not rendered HTML — cart, checkout, and my-account pages stay fully dynamic while every product lookup, taxonomy query, and options table read gets served from memory instead of MariaDB. This is the opposite of full-page cache, which must aggressively exclude those same pages or shoppers see stale carts and broken nonces.

Install Redis, then a persistent object-cache drop-in (Redis Object Cache or similar) so WordPress's internal object cache — which normally resets on every request — persists between page loads. This is what makes repeated wp_postmeta lookups for _sku and _price fast on category pages with hundreds of products, without touching disk-based page cache at all.

TIP

Never point a full-page caching plugin at cart, checkout, or my-account URLs. If you use one for the catalog's public pages, set explicit exclusion rules or a Cache-Control: no-cache header on those routes — Redis object cache alone is enough for most stores and carries none of that risk.

Image Optimization and CDN Offload

Product images are usually the heaviest asset on a WooCommerce page, and every unoptimized gallery thumbnail directly taxes both origin bandwidth and Core Web Vitals. Convert product uploads to WebP or AVIF on upload, generate only the image sizes your theme actually registers (WooCommerce and popular themes can register a dozen or more redundant crops), and strip EXIF metadata that adds weight for no visual benefit.

Once images are optimized, stop serving them from the VPS at all. Routing product media through Cloudflare or another CDN takes repeat image requests off your origin's Nginx and disk I/O entirely, which matters most during traffic spikes when PHP-FPM and MariaDB need every spare CPU cycle for actual checkout logic. CloudStick's built-in Cloudflare integration lets you turn on this proxying and edge caching for a site's static assets without leaving the dashboard.

Nginx Static Asset Caching Headers

Long-lived cache headers on CSS, JS, fonts, and images let browsers and CDNs skip revalidation entirely on repeat visits, which is free performance you get without touching PHP. Add a dedicated location block matching static file extensions to your site's Nginx config, separate from the block that proxies PHP requests to PHP-FPM.

# /etc/nginx-cs/sites-enabled/<site>.conf
location ~* \.(jpg|jpeg|png|webp|avif|gif|svg|css|js|woff2?)$ {
expires 30d;
add_header Cache-Control "public, immutable";
access_log off;
try_files $uri =404;
}

Do not put a matching rule anywhere near /wc-ajax/, /cart/, /checkout/, or /my-account/ — those must keep hitting PHP on every request so cart fragments, nonces, and session state stay correct. Keep the two location blocks clearly separated and order-tested with nginx -t before reloading.

Next Steps

Roll these changes out in order of impact: right-size the VPS and PHP-FPM pool first, enable Redis object cache second, then layer on image optimization, CDN offload, and static asset headers. Each layer compounds — a well-tuned PHP-FPM pool with no object cache still hammers MariaDB on every catalog page, while Redis without adequate pm.max_children just moves the bottleneck to queued PHP workers.

Also move WP-Cron off the default pseudo-cron trigger once traffic grows — a real system cron hitting wp-cron.php hourly (or WP-CLI's wp cron event run --due-now) keeps stock holds and scheduled cleanup jobs from firing mid-request and adding latency to a random shopper's page load. Provision the server, apply these five changes, and load-test checkout before a real sale finds the ceiling for you.

Leave a comment
Full Name
Email Address
Message
Contents