guide

Hosting WooCommerce at Scale: The 2026 Engineering Guide

Hosting WooCommerce at Scale: The 2026 Engineering Guide
NC 15 min read

WooCommerce runs comfortably on modest hosting until it doesn't. The transition is usually abrupt: a store that handled last year's peak fine spends this year's returning 502s while the database sits pinned at 100% CPU.

The failure is rarely mysterious. WooCommerce at scale breaks in three predictable places, and each has a known architectural answer. This guide covers what those bottlenecks are, why they happen, and how to build infrastructure that absorbs traffic spikes instead of collapsing under them.

It's written for practitioners — the reasoning matters more than the recipe, because your store's numbers won't match anyone else's.

WooCommerce at scale fails on three things: database write volume during checkout, PHP worker exhaustion under concurrency, and uncached cart and session traffic. The fixes are stateless application nodes behind a load balancer, sessions and object cache in Redis, layered caching (CDN, full-page, fragment), and a database sized for write throughput rather than storage. Cache aggressively, keep nodes stateless, and size for your worst hour rather than your average one.


The Three Bottlenecks

Understanding why WooCommerce fails under load is what makes the architecture make sense.

1. Database write volume

Every order triggers a substantial burst of database writes — order records, line items, order meta, inventory decrements, customer records, and background jobs queued for downstream processing. The exact count varies with your plugin set, but it's dozens of writes per order, not a handful.

That's fine at ten orders an hour. At two thousand orders an hour during a sale, write volume becomes the constraint that determines whether checkout completes or times out. And unlike reads, writes can't be cached or served from a replica — they all land on the primary.

This is why database capacity is the real ceiling on a WooCommerce store, not application server count. You can add application nodes indefinitely and still fail if the database can't absorb the writes.

2. PHP worker exhaustion

PHP-FPM handles one request per worker. A node with forty workers serves forty concurrent dynamic requests — the forty-first queues, and if the queue grows faster than it drains, requests time out.

The arithmetic is worth doing explicitly, because it explains why caching matters more than hardware:

  • Concurrent visitors × (1 − cache hit rate) = concurrent dynamic requests

  • Concurrent dynamic requests ÷ workers per node = nodes required

Run that with a low cache hit rate and the node count is absurd. Run it with a high hit rate and it's modest. The difference between those two scenarios is a caching configuration, not a hardware budget — which is why caching is the highest-leverage work available to you.

3. Session and cart state

WordPress stores sessions in the database by default. For logged-in users and anyone with an active cart, that means database writes on page views that would otherwise be pure reads.

During a sale, when a large share of visitors have carts, this converts a read-heavy workload into a write-heavy one — precisely when you have least write headroom. Moving sessions out of the database is one of the most consequential single changes available, and it's also what makes stateless nodes possible.


The Architecture

Component

State

Scaling

Application nodes (PHP-FPM)

Stateless

Horizontal — add nodes freely

Database

Stateful

Vertical, then read replicas

Redis

Stateful, shared

Vertical; cluster at high scale

Object storage

Stateful

Effectively unlimited

CDN

Cache

Absorbs most requests

The stateless application node is the whole point. A node holding no local state — no uploaded files, no sessions, no cache that matters — can be created and destroyed freely. That's what makes autoscaling work. Any node holding state that other nodes need is a node you can't safely terminate.

Three things commonly break statelessness and need moving off the node:

  • Uploads → object storage

  • Sessions → Redis

  • Object cache → Redis

Get those three right and horizontal scaling works. Miss any one and you'll find new nodes serving broken pages or losing customer carts.


What You Need Before Starting

Your codebase in a Git repository. Theme, child theme, and ideally plugin dependencies managed through Composer rather than committed binaries. GitHub and Bitbucket both connect.

A migration plan sized to your database. Small databases move with a dump and restore. Large ones need replication-based migration, because a dump-and-restore cutover means downtime proportional to database size.

S3-compatible object storage for media. Non-negotiable if you want autoscaling, for the reasons above.

A CDN. Static assets and cacheable HTML should be served from the edge, not from your application nodes.

Redis for object cache and sessions.

A performance baseline. Your current p95 page load, peak orders per hour, and cache hit rate. Without these you can't tell whether changes helped, and you can't size anything sensibly.


Build and Deployment

Deploy from Git, not FTP. Every release becomes a reviewable, revertible commit. For a store, that audit trail matters — when checkout breaks, the first question is what changed. Git-based deployment with auto-deploy handles this.

Pin your PHP version explicitly and keep it current. WordPress and WooCommerce both perform measurably better on recent PHP releases, and the gap is large enough to matter on checkout endpoints specifically. Don't let the runtime version be whatever the platform happens to default to — declare it.

Build steps typically include installing Composer dependencies with development packages excluded and autoloader optimization enabled, plus any theme asset compilation. If your theme has a build pipeline, it belongs in the deployment build rather than in committed compiled assets.

Confirm required PHP extensions are available — database drivers, Redis, image processing, and internationalization are the usual set for WooCommerce.

Keep configuration in environment variables, not committed config files. Database credentials, Redis connection details, and API keys belong in environment variables, injected at runtime. Anything committed to Git is permanent, even after deletion.

If you'd rather not assemble the runtime layer yourself, NevTan Cloud offers a managed WordPress option.


Data Services

Colocate the database with your application nodes. Every page view generates multiple queries, and cross-region latency multiplies across all of them. Same-region placement is the cheapest performance win available.

Size the database for write throughput, not storage capacity. Storage is rarely the constraint; write IOPS and connection capacity are. See managed databases for available engines and options.

Watch the connection pool. Each application node maintains database connections. Ten nodes multiply that tenfold, and connection limits are finite. Past a certain point, adding nodes exhausts the pool and makes performance worse — a genuinely counterintuitive failure mode that catches teams mid-incident.

Consider read replicas once catalog queries dominate. Product listings, search, and reporting are read-heavy and can be offloaded, keeping the primary free for checkout writes.

If you use replicas, monitor replication lag. A lagging replica serves stale data — including stale stock counts, which means overselling inventory you don't have. That's a business problem, not just a technical one, and it deserves its own alert.

Redis configuration

Redis serves two distinct purposes, and both matter:

Object cache — stores results of expensive queries so repeated requests don't re-run them.

Session handler — moves session data out of the database, removing write load and making nodes stateless.

Set an eviction policy. WooCommerce object caches grow quickly during sales. Without one, Redis eventually exhausts memory and starts failing writes rather than discarding old entries. A least-recently-used policy handles this without intervention.

Monitor hit ratio. A low ratio means the cache isn't working and you're paying for Redis while still hitting the database. It usually indicates undersized memory or overly aggressive invalidation.


Caching: Three Layers

This is where WooCommerce at scale is won or lost. Each layer catches what the previous one missed.

Layer 1 — CDN

Static assets (images, CSS, JavaScript) cache at the edge with a long TTL. Anonymous HTML can cache for a short interval — long enough to absorb a burst, short enough that price and stock changes propagate quickly.

Purge on content change rather than relying purely on TTL expiry. A product update should invalidate that product's cached pages immediately.

This layer absorbs the largest share of total requests for a typical store.

Layer 2 — Full-page cache

Cached HTML for anonymous visitors, served without invoking PHP at all. This is what lets a modest number of nodes serve a large number of browsers.

The critical part is the bypass rules. Cache must be bypassed for:

  • Cart, checkout, and account pages

  • Any request carrying a cart or session cookie

  • Logged-in users

  • Anything with personalized pricing

Getting this wrong is the single most damaging mistake in WooCommerce caching, because the failure mode is customers seeing each other's carts — a privacy incident and a checkout failure at once. Test bypass rules explicitly with a populated cart before going live.

Layer 3 — Fragment cache

Expensive page components cached individually: related products, recently viewed, category sidebars, cross-sell blocks. This keeps pages fast for logged-in users who can't benefit from full-page cache.

Cart fragments deserve specific attention. The AJAX request updating the cart count in your header runs on every catalog page view and is uncacheable by nature. Serving it from a lightweight dedicated path, rather than bootstrapping the full application, meaningfully reduces load on catalog pages — usually your highest-traffic pages.


Scaling Configuration

Set a minimum above one. A single node means every restart, deploy, and crash is downtime. For commerce, downtime is abandoned carts that don't come back.

Set a maximum. It protects against a retry storm or traffic anomaly scaling you into a cost incident.

Scale on CPU or request queue depth, not memory. PHP-FPM holds memory even when idle, so memory-based triggers cause constant flapping — scaling up and down repeatedly while actual load is steady. CPU tracks real work; queue depth is often an even better leading indicator, since it rises before CPU saturates.

Use asymmetric cooldowns. Scale out quickly, scale in slowly. Aggressive scale-in during a spike removes capacity you're about to need again. There's little cost to keeping a node an extra few minutes; there is real cost to not having it when traffic returns.

Account for node startup time. A new node must boot, warm up, and pass health checks before serving traffic. If that takes a minute, scaling at the moment you saturate means a minute of degraded service. Scale earlier than feels necessary, and keep enough baseline capacity to absorb the start of a spike while autoscaling catches up.

Disable sticky sessions if you can. Once sessions live in Redis, any node can serve any request, giving you true round-robin distribution. Sticky sessions create hot nodes — one instance handling disproportionate load while others idle.

Health checks should verify dependencies, not just that the process started. An endpoint returning 200 only when PHP, database, and Redis are all reachable prevents traffic routing to a node that can't serve. See scaling presets.


Observability

Five metrics matter more than the rest:

Metric

What it tells you

Checkout p95 latency

The number that maps most directly to revenue

Database write throughput

Your actual capacity ceiling

Redis hit ratio

Whether caching is working

PHP-FPM queue depth

Whether you need more nodes right now

5xx error rate

Whether customers are hitting failures

Add business metrics alongside technical ones. Orders per hour against its normal pattern catches failures technical metrics miss entirely — a checkout broken for one payment method or one region may not move your error rate detectably, but it shows up immediately in order volume.

Alert on p95 and p99, not averages. Averages hide the tail, and the tail is the customer who gave up.

Wire container metrics and custom application metrics through metric ingestion so infrastructure and business signals sit in one view.

Backups and rollback

Automate database backups with point-in-time recovery. Order data isn't reconstructible — a store that loses orders has lost the fulfillment obligations and customer relationships attached to them.

Test a restore periodically. An untested backup is a hypothesis. Quarterly is reasonable; the first test usually surfaces something.

Keep rollback available. Git-based deployment means every release is a commit you can revert. Logs and rollbacks preserve the failed deploy's output for diagnosis while you restore service.

Note the asymmetry: rolling back code doesn't roll back database changes. If a plugin update ran a schema migration, reverting the code may leave old code against a new schema. Test updates in staging specifically for this.


Choosing Your Architecture

Size by peak, not average. A store with modest typical traffic but hard seasonal spikes needs to be built for the spike.

Store profile

Architecture

Low order volume, small catalog

Single node plus managed database and Redis; skip replicas

Moderate volume, seasonal peaks

Autoscaling nodes, one read replica, full-page cache, CDN

High volume, large catalog

More nodes, multiple replicas, dedicated Redis, queued order processing

Very high volume or complex B2B pricing

Consider headless architecture or regional sharding

The question that determines your tier isn't average orders per day — it's what happens during your worst hour of the year. Build for that, then let autoscaling handle everything quieter.

B2B stores need different thinking. Role-based pricing and customer-specific catalogs destroy cache hit rates, because pages can't be shared between customers. Fragment caching for pricing blocks, role logic in Redis, and potentially a headless frontend all become more attractive as unique pricing arrangements multiply.


Common Mistakes

Storing uploads on the application node. Breaks statelessness completely. New nodes won't have the images, and terminated nodes take files with them. Object storage, always.

Caching cart and checkout pages. Customers see each other's carts. A privacy incident, not just a bug. Bypass cache on any request carrying cart or session cookies.

Ignoring the background job table. WooCommerce queues scheduled actions in the database, and that table grows enormous on busy stores. Without cleanup and proper indexing it becomes your slowest query — and it runs constantly.

Scaling on memory. PHP-FPM's memory behavior makes memory-based triggers flap. Use CPU or queue depth.

Skipping load testing. Testing at expected peak tells you nothing about your breaking point. Test well above it, so you discover limits in a controlled window rather than during a sale.

Adding nodes when the database is the bottleneck. More nodes consume more database connections. Past the pool limit, scaling out makes things worse. Diagnose which layer is saturated before adding capacity to the wrong one.

Updating plugins in production. A plugin update is a code change with checkout in its blast radius. Test in staging, ship as a commit, keep rollback ready.

Forgetting replication lag. Stale replicas show stale stock and you oversell. Alert on lag specifically.


Frequently Asked Questions

What actually limits how much traffic a WooCommerce store can handle?

Usually database write throughput, not application capacity. Application nodes scale horizontally without much difficulty, but every order generates a burst of writes that must land on the primary and can't be cached or offloaded to a replica. When stores fail under load, the database is typically saturated while application nodes still have headroom.

Do I need a read replica?

Once read traffic — product listings, search, reporting — meaningfully competes with checkout writes on the primary. Large catalogs and high browse-to-order ratios both push you there sooner. Below that point, a single well-tuned instance is simpler and avoids replication lag as a failure mode. Watch primary CPU and write latency during peak to know when you've crossed over.

Why do sessions need to move to Redis?

Two reasons. First, database-backed sessions turn page views into database writes for anyone with a cart, converting a read-heavy workload into a write-heavy one exactly when you have least write headroom. Second, sessions on the node make nodes stateful, which breaks autoscaling — a customer routed to a different node loses their cart.

Can I run WooCommerce at scale without a CDN?

Technically yes, at considerably higher infrastructure cost. A CDN absorbs the large majority of requests for a typical store — images, CSS, JavaScript, and cacheable anonymous HTML. Without one, all of that reaches your application nodes, forcing you to provision far more capacity to serve traffic that should never have arrived.

How do I handle plugin updates safely?

Never in production. Use a staging environment mirroring production, test there including a full checkout flow, then ship the update as a commit through your normal pipeline. Git-based deployment means a bad update is a revert rather than a manual repair. Watch for updates that run schema migrations, since those don't revert with the code.

What happens during a spike if autoscaling can't keep up?

There's always a gap between load arriving and new nodes serving traffic — boot, warm-up, and health checks all take time. Cover it with baseline capacity above your minimum need and aggressive caching, which absorbs the initial surge while scaling catches up. For known events like scheduled sales, scale up in advance rather than relying on reactive autoscaling.

Is WooCommerce suitable for B2B with complex pricing?

Yes, with caveats. Customer-specific pricing and catalogs mean pages can't be shared between customers, which collapses full-page cache effectiveness — your highest-leverage optimization stops applying. Fragment caching for pricing blocks and role data in Redis both help. Past a certain number of accounts with unique pricing, a headless frontend that fetches pricing separately from page content becomes the cleaner architecture.

How long should migration from shared hosting take?

It scales with database size. Small stores can move in a single maintenance window including DNS propagation. Large stores need replication-based migration — set up replication, let it catch up, test thoroughly, then cut over — which is weeks of elapsed time but minimal downtime. Never migrate during peak season, and never cut over on a Friday.


Getting Started

The architecture is consistent regardless of store size: stateless application nodes, sessions and object cache in Redis, media in object storage, a database sized for write throughput, and three layers of caching in front of all of it.

What changes with scale is node count, whether you need read replicas, and how much engineering attention caching deserves. The bottlenecks don't change — database writes, PHP workers, and session state are the constraints at every size.

Start by measuring your current baseline, then fix statelessness first, caching second, and scaling last. Scaling infrastructure that isn't stateless and isn't cached just multiplies the problem.

App platform docs · Database docs · Monitoring docs · WordPress on NevTan Cloud