Web Hosting

High-Traffic WooCommerce Hosting Without Flash-Sale Crashes

Priya NairPriya NairOctober 27, 2026

Flash sales break WooCommerce in a predictable order: PHP workers, then MySQL writes, then the checkout itself. This guide walks the cache rules that keep product pages static, the Redis setup that stops session stampedes, and the InnoDB settings that decide whether a cart survives the first ninety seconds of traffic.

A flash sale starts at 9am. By 9:04 the product pages are fine and the checkout is returning 504s, which is the worst possible split: the traffic arrived, the catalogue served, and the orders did not.

That pattern is normal, and it is not really a WordPress problem. Cached pages are cheap. Carts, sessions and stock updates are not, and they all land on PHP and MySQL at the same moment.

This is the architecture that survives a high traffic WooCommerce sale: what to cache, what must never be cached, how to size the worker pool, and which database settings matter under write load. It assumes a VPS you can configure. If you ship twenty orders a week, you do not need any of it, and a managed plan will do.

Why the checkout fails first

When a shopper adds something to the cart, WooCommerce sets a session cookie. The web server sees that cookie and stops serving the cached copy of the page, because a cached page cannot contain one person's cart. Every request from that visitor now boots WordPress, loads plugins and queries MySQL.

That is correct behaviour. It is also the moment your server stops benefiting from caching. A shopper who adds a product and then browses four more pages generates five full WordPress boots and dozens of queries, none of them cacheable.

Under a flash sale the sequence is predictable.

  1. PHP workers fill with cart requests. New requests queue, then time out, and the browser shows a 502 or 504.

  2. MySQL queues writes. Order inserts and stock decrements contend for locks on the same product rows.

  3. Disk I/O becomes the ceiling if storage cannot absorb the transaction log writes.

The rest of this article is about staying out of step three, because when the database waits, nothing else matters.


Cache rules that keep checkout alive

The rules are boring, and they matter more than any tuning parameter.

  • Cache product, category and content pages for anonymous visitors.

  • Never cache /cart/, /checkout/, /my-account/, or any request carrying a session cookie.

  • Exclude the WooCommerce AJAX endpoint (?wc-ajax=) from the page cache.

  • Shorten the TTL during a sale, and warm the popular URLs before it starts.

  • Vary by currency and country if you sell in more than one.

Getting this wrong in either direction is expensive. Cache the cart and you will show one shopper another shopper's totals. Cache nothing and every product view hits PHP, which turns a browse-heavy spike into a database problem.

WooCommerce's high-performance order storage moves order data out of the post tables, which cuts the write cost of each order [1]. Enable it before a sale, not during one, because the migration itself is write-heavy.


The four layers of a resilient stack

[ Shopper ]
     β”‚
     β–Ό
[ web server + full-page cache ]  catalog pages served from memory
     β”‚  cart, checkout and account requests bypass the cache
     β–Ό
[ PHP-FPM pool + OPcache ]  pages that need WordPress
     β”‚
     β”œβ”€β”€> [ Redis object cache ]  repeated queries answered from RAM
     β–Ό
[ MySQL / MariaDB + InnoDB buffer pool ]  orders, stock, sessions

Redis, and what it actually buys

A high traffic WooCommerce store reloads a lot of the same data on every request: options, post meta, product attributes, tax rates, shipping zones. With no object cache, each of those is a MySQL query. With one, the result sits in RAM and is reused for the next request [2].

The benefit depends on repetition. A catalogue where every product page pulls the same options and term metadata gains a great deal. A store where every shopper's page is unique gains less.

Redis does not speed up writes. It reduces reads, which leaves the database free for the order inserts and stock decrements that decide whether checkout completes. That is why a store can report a healthy cache hit rate and still fall over under write load.

The drop-in is easy to verify with WP-CLI:

# Verify Redis is actively caching WordPress objects via CLI
wp redis status
# Status: Connected
# Client: PhpRedis (v6.0+)
# Drop-in: Valid

If the drop-in is not installed, nothing else in this section matters, because WordPress is still asking MySQL for everything.


MySQL settings that matter

For high traffic WooCommerce, the database is where a flash sale is won or lost. Orders have to be committed, stock has to be decremented, and both take locks. Default settings are tuned for a small application on a shared machine, not for a write burst against the same product rows.

Create /etc/mysql/conf.d/woocommerce-performance.cnf:

# /etc/mysql/conf.d/woocommerce-performance.cnf
[mysqld]
# Keep active tables and indexes in memory (70-80% of RAM on a dedicated DB host)
innodb_buffer_pool_size = 12G
innodb_buffer_pool_instances = 8

# Larger redo capacity absorbs write bursts before checkpointing (MySQL 8.0.30+)
innodb_redo_log_capacity = 2G
innodb_log_buffer_size = 64M

# fsync once per second rather than per commit: faster writes, and up to one
# second of committed transactions can be lost if the OS or host crashes.
innodb_flush_log_at_trx_commit = 2
innodb_flush_method = O_DIRECT

# Connections and file handles
max_connections = 500
thread_cache_size = 64
table_open_cache = 8000
open_files_limit = 65535
  • innodb_buffer_pool_size: keeps active tables and indexes resident instead of reading from disk. Size it against the actual working set, and watch the buffer pool hit rate rather than guessing from total RAM [3].

  • innodb_flush_log_at_trx_commit = 2: flushes the log once per second instead of on every commit. That is a real throughput gain under write bursts, and a real durability window: a host crash can lose the last second of committed orders. If a confirmed order must survive that, leave it at 1 and buy faster storage instead.

  • max_connections: set it from PHP workers multiplied by connections per worker, not from a blog post. Connections beyond available RAM cost more than they add.

Do not carry an old 5.7 configuration forward either. MySQL 8 removed the query cache, and a leftover query_cache_type line will stop the server from starting.

If you are choosing an engine for a new store rather than tuning an existing one, the MySQL vs PostgreSQL comparison covers where each one is the better default.


Cart fragments: the quiet CPU tax

WooCommerce enqueues cart-fragments.js, which fires an AJAX request (?wc-ajax=get_refreshed_fragments) on page views so the header cart count stays accurate. That request needs the session, so it bypasses the page cache and boots WordPress. On a browse-heavy catalogue it quietly converts cached page views into uncached PHP requests.

For most stores the fix is to stop loading the script outside cart and checkout:

// functions.php - Disable Cart Fragments on non-cart pages
add_action('wp_enqueue_scripts', function() {
    if (function_exists('is_woocommerce')) {
        if (!is_cart() && !is_checkout()) {
            wp_dequeue_script('wc-cart-fragments');
        }
    }
}, 99);

That is the documented dequeue call [4]. Test the header cart count afterwards: some themes render it server-side, others rely on the fragment refresh and will show a stale count until the next page load. If the count matters, refresh it on the add-to-cart event rather than on every page view.

Speed is also a ranking input, which we covered in why website speed matters for SEO.


Sizing the box without inventing numbers

Sizing high traffic WooCommerce from a vendor table is guesswork. The concurrent-user figures are not reproducible, because they depend on session length, catalogue size, plugin count and how much of the traffic is bots. What transfers between stores is the symptom-to-limit mapping.

Symptom at peak

Likely limit

First move

502 or 504 with CPU well under 100%

PHP workers exhausted, or waiting on I/O

Recalculate pm.max_children against RAM; check disk wait

Slow checkout, flat CPU, growing MySQL queue

Write I/O or lock contention

Check storage, buffer pool size, long transactions

Slow catalogue pages for anonymous visitors

Page cache miss

Fix cache rules and TTLs; warm popular URLs

Rising memory, OOM killer

Too many workers for the RAM

Reduce workers or add RAM

Slow only for logged-in shoppers

Object cache miss or session table growth

Check Redis hit rate; prune expired sessions

The useful number is not how many concurrent shoppers a box can hold. It is how many PHP workers it can hold, and how many requests per second one worker serves on your catalogue. Measure the second with a load test and the first becomes arithmetic.

Test the sale before the sale

Load test with a script that behaves like a shopper: browse, add to cart, reach checkout, abandon. k6 or ab will do. The sequence matters, because a GET-only test never reproduces the cache bypass that causes the outage.

Run it against a staging copy with production cache rules. Watch four things: PHP worker saturation, MySQL queue depth, disk wait and error rate. If the error rate climbs while the CPU stays flat, you have an I/O or lock problem, not a compute problem.

Two habits help more than tuning. Freeze plugin and theme updates for 48 hours around the sale, and warm the ten most popular URLs a few minutes before the start.

What this costs you

None of it is free. Redis is another service to monitor. Tuned InnoDB settings are a per-host decision that has to be revisited when the box changes. A load test is an afternoon you will not get back.

The trade is straightforward. A store doing a few orders an hour does not need any of this, and a managed plan with caching enabled is cheaper than the hours spent tuning. A high traffic WooCommerce store running four flash sales a year, each worth more than the annual hosting bill, needs all of it and a rehearsal before each one. The managed vs self-managed hosting comparison covers what you are actually buying in each case, and ecommerce hosting is the managed version of the stack described here.

One limitation worth stating. This assumes a single database host. Once write volume outgrows one machine, the next step is splitting reads from writes, and that is a different project with a different set of failure modes.

Share

Comments

No comments yet. Be the first to comment!

Leave a Comment

Your email will not be published. Comments are reviewed before appearing. Protected by reCAPTCHA β€” Privacy & Terms