Why Is My WooCommerce Membership Site Slow? 8 Real Causes and Fixes

Muhammad Ahmad Written by Muhammad Ahmad
woocommerce membership site slow illustration

Nearly every time someone tells me their WooCommerce membership site slow problem started after a redesign, the redesign turns out to be innocent. The site was already slow. It just was not slow for the person testing it, because the person testing it was logged out, and a logged out visitor on a membership site is looking at a completely different machine than a member is.

That gap is the whole story. Run your home page through PageSpeed Insights and you get a green score, because PageSpeed arrives as an anonymous visitor and gets handed a cached file. Your paying member logs in, hits their account page, and waits four seconds for a page that had to be built from scratch by PHP and MySQL on every single load. Same site. Same server. Two entirely different experiences, and only one of them is being measured.

I build and maintain WordPress membership and course platforms for a living, and the causes below are the ones I actually find, in roughly the order I find them. Every one of them is fixable without rebuilding anything.

Quick Answer

A WooCommerce membership site is slow because logged in members bypass page caching entirely, so every member page load runs the full PHP and database stack instead of serving a static file. The four things that make that uncached load heavy are a bloated wp_options autoload (WordPress recommends staying under 800 KB), an Action Scheduler table grown to millions of rows, cart fragment requests firing on pages with no cart, and orders still living in wp_posts rather than the newer HPOS tables. Fix those and add a persistent object cache. Do not fix it by caching member pages, which breaks content restriction.

๐Ÿ“‹ What’s Covered

Why Is a WooCommerce Membership Site Slower Than a Normal Store?

Because a normal store gets to lie to most of its visitors, and a membership site does not.

On a plain content site, a page cache sits in front of WordPress. The first visitor triggers PHP, the resulting HTML gets saved as a file, and the next ten thousand visitors get handed that file without WordPress running at all. That is why caching plugins produce such enormous wins. WordPress’s own optimization documentation puts it plainly, describing caching as the place you get the biggest benefit for the smallest hassle.

A membership page cannot be that file. What it shows depends on who is asking. Member A sees three unlocked courses, member B sees one and an upgrade prompt, and an expired member sees a renewal notice. Serve one saved copy to all three and you have either leaked paid content to someone who did not pay for it or hidden it from someone who did.

So WordPress does the only safe thing and skips the cache for anyone holding a login cookie. Every member request runs the full route: PHP boots, plugins load, WooCommerce initialises, membership rules get evaluated against the current user, queries run, HTML gets assembled. It happens on every page, for every member, every time.

That is not a bug, and there is no setting that removes it. It does mean the uncached path is the only path your members ever take, so all the weight you have been carrying invisibly for years lands on them directly. The eight causes below are that weight, itemised.

Diagram comparing a logged out visitor request served from the page cache with a logged in member request that bypasses the cache and runs PHP, plugins and database queries on a WooCommerce membership site
The same page, two completely different request paths. Only one of them is what your members experience.

Cause 1: Why Can’t Page Caching Help Your Logged In Members?

This is the one that makes every speed report you have ever run misleading, so it belongs first.

WP Rocket, W3 Total Cache, LiteSpeed Cache and every serious alternative exclude logged in users from full page caching by default, and they are right to. Performance specialists who work on stores all day describe logged in sessions as several times slower than cached views, with a high time to first byte, and note that on a store those logged in users are usually the most valuable people on the site.

The trap is that your monitoring never sees it. Uptime checks, PageSpeed runs, and your own quick look at the site in a private window all arrive logged out and all get the fast path. Meanwhile the actual customer experience, the one people pay for, runs on the slow path and nobody is watching it.

Fix: Stop trying to cache member pages and make the uncached path cheap instead. Add a persistent object cache, Redis or Memcached, which stores the results of repeated database queries in memory across requests. WordPress’s optimization guide recommends exactly this, noting that without it the server keeps going back to the database for options on every request. On a membership site an object cache also holds access rules, user roles, and restriction checks, which is precisely the work your member pages repeat constantly. Then measure the right thing: test while logged in as a real member, on a real member page, not the home page.

Cause 2: Is Your wp_options Table Loading on Every Single Request?

This is the single most common thing I find, and it is invisible until you go looking for it.

Rows in wp_options carry an autoload flag. Anything flagged yes gets read into memory on every request the site serves, whether the page needs it or not. WordPress’s own optimization documentation recommends keeping total autoloaded data under 800 KB. Plenty of membership sites are running ten or twenty times that.

Plugins cause it. Some write large transients into the options table and set them to autoload without an expiry, so the data never clears itself out. The Transients API documentation is explicit that transients are temporary by design and should never be assumed to be in the database at all, which is exactly why nothing is coming along to tidy up after a plugin that treats them as permanent storage.

The symptom is characteristic: the whole site feels heavy rather than one page being broken, wp-admin drags, and MySQL sits high on CPU with no obvious traffic to explain it. Membership and subscription plugins make it worse because they generate more of this data than a simple store does.

Chart of WordPress autoloaded options size thresholds showing under 800 KB as the recommended level, around 1 MB as a measurable performance hit, and 10 MB and above as critical
Autoload thresholds as published in the WordPress optimization documentation and its linked support guidance.

Fix: Measure it before you touch it. Run SELECT SUM(LENGTH(option_value)) FROM wp_options WHERE autoload = 'yes'; in phpMyAdmin to get your real total in bytes, then list the twenty largest autoloaded rows by name. Expired transients are safe to delete outright, since anything that needs them regenerates them. For the rest, identify which plugin owns each large row before removing anything, because some of it is genuinely required. Where a row is large but rarely needed, flipping its autoload flag to no is safer than deleting it. Retest the total afterwards and confirm you actually crossed back under 800 KB rather than assuming you did.

Cause 3: Why Do Cart Fragments Slow Down Pages That Have No Cart?

WooCommerce keeps the little cart counter in your header accurate using an AJAX call to ?wc-ajax=get_refreshed_fragments. It has to bypass the page cache to do that, because the point of the request is to read live session state that a cached page cannot know.

The problem is where it fires. On a default setup that request goes out on every page, including your lesson pages, your account dashboard, your blog posts, and your contact page. None of those pages contain a cart. Every one of them still pays for the lookup, and on a busy server that single request has been reported taking more than a second or two to come back.

On a membership site the arithmetic gets worse than on a shop, because members read many pages in a session rather than browsing a handful of products. Twenty lesson pages in an evening is twenty uncached, PHP consuming requests that produced nothing the member needed.

Fix: Confirm it first. Open your browser network tab on a lesson page and filter for wc-ajax. If the request is there on a page with no cart on it, you have found real waste. Both WP Rocket and Perfmatters ship a setting for this, and the sensible version of the fix is conditional rather than absolute: keep fragments running on shop, cart, and checkout pages, and disable them everywhere else. Removing them site wide breaks live cart counters, so if your header shows one, keep the conditional approach. Then verify by reloading a lesson page and confirming the request is gone.

Cause 4: Has Action Scheduler Quietly Become Your Biggest Table?

If you sell recurring memberships, this one is nearly guaranteed and almost nobody knows to look for it.

Action Scheduler is the background job queue WooCommerce and WooCommerce Subscriptions run on. Every renewal, every expiry check, every status change, every webhook queues an action and writes a row into wp_actionscheduler_actions, with matching rows in wp_actionscheduler_logs. Completed rows are supposed to be cleaned up on a retention schedule. On a lot of real sites that cleanup falls behind and never catches up.

What you end up with is a site that has been running for three years and has millions of rows in a table it queries constantly. It is a known enough problem to have its own open issue on the WooCommerce repository about the tables bloating with succeeded actions. Every new job then has to be found and claimed inside that table, so the queue itself gets slower, which is how a backlog turns into a bigger backlog.

Fix: Look at WooCommerce, Status, Scheduled Actions and read the real counts per status. Completed, failed, and cancelled rows are safe to clear; pending rows are not, so never empty the table wholesale. Clear the backlog in batches rather than one enormous delete, which locks the table on a live site. Then stop it recurring by shortening retention with the action_scheduler_retention_period filter, which defaults to 30 days, or by scheduling a regular cleanup. Also check whether something is failing repeatedly, because a persistent backlog of failed actions is usually a broken integration rather than a storage problem.

Cause 5: Are Your Orders Still Stored in wp_posts Instead of HPOS?

Older WooCommerce stored every order as a post in wp_posts, with every order detail as a separate row in wp_postmeta. One order became dozens of meta rows, sitting in the same tables as your pages, lessons, and revisions.

WooCommerce replaced that with High Performance Order Storage, which moves orders into four dedicated tables with proper indexes. Its own documentation describes the result as fewer read and write operations and fewer busy tables, and HPOS has been enabled by default for new installations since WooCommerce 8.2.

The catch is the phrase “new installations.” A membership site that has been running since before 8.2 was not switched over automatically. It is still storing orders the old way, and because a subscription membership generates a fresh renewal order per member per month, the volume climbs steadily. Three years of monthly renewals across a few hundred members is a lot of rows in a table your entire site reads from.

Fix: Check WooCommerce, Settings, Advanced, Features to see which storage your site is actually on. If it is still the legacy option, take a full backup, enable compatibility mode so data syncs into the new tables, let the sync finish completely, and only then switch the preferred storage to high performance order storage. Test on staging before production, because a plugin that reads orders directly from postmeta with custom SQL will break. Leave compatibility mode on for a while afterwards so you can roll back cleanly if something surfaces a fortnight later.

Cause 6: Are Member Discounts Recalculated on Every Product View?

Member pricing is one of the best reasons to run memberships alongside a store, and it is also quietly expensive.

WooCommerce Memberships lets a plan grant discounts across all products, specific products, or whole categories. To display the right price, the site has to work out, for this visitor, which plans they hold, whether each plan is active, which rules apply to this product, and which discount wins. That is a real evaluation, not a stored number, and it runs per product.

On a single product page nobody notices. On a shop archive showing thirty six products it runs thirty six times, plus once more for the sorting and once more for any price filter widget. Stack a second membership plan and a category level rule on top and the work multiplies rather than adds.

Fix: Cut the number of evaluations rather than the logic. Reduce products per archive page from thirty six to twelve or eighteen, which alone removes two thirds of the calls on that page. Prefer one rule covering a category over twenty rules covering twenty products, since fewer rules means a shorter list to walk for every product. Drop price filter and sorting widgets you do not genuinely use, because each one forces its own pass over the full price set. A persistent object cache helps here too, since it keeps the member’s plan lookups in memory rather than re-querying them for every product in the loop.

Cause 7: Do You Have Enough PHP Workers for Logged In Traffic?

Here is where hosting stops being a line item and starts being the actual problem.

A PHP worker is one process that can build one page at a time. Cheap shared hosting gives you very few of them, on the reasonable assumption that a cache will absorb nearly all your traffic. That assumption holds for a brochure site. It collapses for a membership site, where every logged in request needs a worker and none of them can be served from cache.

Once workers are saturated, requests queue. Queued requests do not fail, they wait, which is why the site feels slow rather than broken. Then cart fragment requests join the queue alongside real page loads, and a small burst of members logging in at the same time turns into a site that takes ten seconds to respond for everyone. Hosting guidance for membership sites is consistent on this point: dynamic, database heavy sites need more memory and more processing headroom than a cached content site does.

Fix: Work out your real concurrency before buying anything. Take your peak concurrent logged in members, which is a number your analytics can give you rather than a guess, and check it against your plan’s worker count. Ask your host directly how many PHP workers you have, since it is rarely on the pricing page. Confirm you are on a current PHP release and that OPcache is on, both of which are free wins. If you are on shared hosting with a real membership base, move to a plan with dedicated resources and a persistent object cache available. This is the one cause on the list you cannot fix with configuration alone.

Cause 8: Is Your Caching Plugin Breaking Member Access Instead of Helping?

This is the failure that arrives after someone tries to fix everything above by turning caching up as far as it goes.

WooCommerce’s own Memberships documentation is direct about it. When members cannot reach their content, or non members can reach restricted content, the first suspect is a caching issue, and that suspicion is strongest when the problem is intermittent or only affects some people. The doc names the usual plugins and recommends temporarily disabling caching rather than clearing it, since clearing a cache proves nothing when the next request just rebuilds the same bad copy.

Intermittent is the important word. A restriction bug caused by caching does not fail every time, which is why it survives testing and gets reported by a member weeks later. By then someone has changed three other settings and the trail is cold.

Fix: Follow the documented approach. Disable caching and optimization plugins temporarily, confirm restrictions behave correctly with them off, then bring them back one at a time until the problem returns. Once you know which plugin it is, exclude member content, account pages, cart, and checkout from page caching in that plugin’s settings rather than turning it off for good. Keep aggressive caching for anonymous visitors, where it is genuinely safe and genuinely fast. And test the result as a real member, a real non member, and a real expired member, because those are three different outcomes and only testing all three proves the rules work.

How Do You Diagnose a Slow WooCommerce Membership Site in the Right Order?

Order matters here, because the cheap checks rule out the expensive ones. This is the sequence I work through, and it usually finds the answer before step five.

Six step diagnostic sequence for a slow WooCommerce membership site, running from reproducing the problem as a real member through to checking hosting and order storage
The order I work through, cheapest checks first. Most sites give up their answer before step five.

Step 1. Reproduce it as a member. Create a real test member account on a real plan and load the pages members actually use: account, course, lesson, checkout. Record the load time for each. If those are fine and only the shop is slow, you have a different problem and the rest of this list is the wrong list.

Step 2. Compare logged out to logged in. Load the same page in a private window and note the difference. A big gap confirms the uncached path is the bottleneck, which points you at causes one, two, six, and seven. A small gap points at the front end instead: images, fonts, and scripts.

Step 3. Measure autoloaded options. One SQL query gives you the number. Compare it against 800 KB. This is the fastest real answer available and it is frequently the whole story.

Step 4. Read the scheduled actions counts. WooCommerce, Status, Scheduled Actions shows the real totals per status. Hundreds of thousands of completed rows is a finding, not background noise.

Step 5. Install Query Monitor and load a member page. It reports the real query count, the total query time, and which plugin owns the slowest ones. A member page running several hundred queries is telling you exactly where to look next.

Step 6. Check hosting and storage. Confirm your PHP worker count, your PHP version, whether an object cache is active, and whether orders are on HPOS. These are the structural items, and they are the ones worth fixing last because the earlier steps often make them unnecessary.

Why Does a Membership Site Get Slower Every Single Month?

Because almost everything a membership site does adds a row somewhere and removes nothing.

A content site’s database is roughly stable. You publish a post, the tables grow slightly, and that is the end of it. A membership site with recurring billing runs on a different curve. Every month produces renewal orders, order meta, subscription status changes, membership notes, scheduled actions, action logs, transients, and session rows. None of that is optional, and by default very little of it is ever cleaned up.

The reason it feels sudden is that database performance does not degrade smoothly. A table performs acceptably while its indexes fit comfortably in memory, and it performs badly once they do not. You cross that line on an ordinary Tuesday, having changed nothing, and the site that was fine last week now is not. The change that broke it happened months earlier and simply took this long to show.

This is also why the same architecture question keeps coming up regardless of platform. Membership builds I have delivered, including the Jen’s Cook course platform on my portfolio, run on subscription billing and drip content, and the structural rule holds everywhere: the more of a page that depends on who is looking at it, the less of that page anything can ever cache. That is a design constraint to plan around rather than a problem to solve later.

The practical conclusion is that a membership site needs maintenance the way a shop needs stock control. It is a recurring job, not a launch task, and the sites that stay fast are the ones where somebody owns it.

How Do You Keep a WooCommerce Membership Site Fast For Good?

Monitor the logged in experience, not the home page. Set up a synthetic check that logs in and loads a real member page on a schedule, and alert on that number. Everything else you monitor is measuring a visitor who is not your customer.

Put database maintenance on a schedule and treat it as routine. Clear completed scheduled actions, delete expired transients, and re-measure autoloaded options once a month. Ten minutes of that beats an emergency at ten times the cost, and the numbers give you a trend line instead of a surprise.

Audit plugins by weight rather than by count. Query Monitor tells you which plugin is responsible for the slowest queries on a real member page, which is a far better basis for removing something than a general feeling that there are too many. One badly written plugin outweighs fifteen well written ones.

Keep a staging site and use it for every structural change. The HPOS migration, the object cache, the caching exclusions, and any plugin removal all deserve a rehearsal. Membership sites carry live billing, so a bad change costs you access complaints and refund requests rather than just a rollback.

Last, decide honestly whether WooCommerce plus memberships is still the right stack for what you are running. It is a strong fit when you genuinely sell products alongside membership. If you are really running courses with a paywall, a dedicated LMS carries less weight per page, and the trade-offs there are the same ones I work through in this guide to hiring a LearnDash developer. Platform level performance problems sometimes have platform level answers, and it is worth asking the question before spending another year optimising around it.

Frequently Asked Questions

Why is my WooCommerce membership site slow only for logged in users?

Because logged in users skip page caching. What a member sees depends on their plan and status, so serving a saved copy would show the wrong content to the wrong person. Every member request therefore runs PHP and the database in full. Logged out visitors get a cached file instead, which is why your speed tests look fine while your members do not agree.

Can I cache pages for logged in members safely?

Not with ordinary full page caching. Some hosts offer per user caching that keeps a separate copy per logged in session, which works but uses far more storage and still needs careful exclusions for cart, checkout, and account pages. The better investment is a persistent object cache, which speeds up the uncached path itself without any risk of showing one member another member’s page.

How much autoloaded data is too much in wp_options?

WordPress’s own optimization documentation recommends staying under 800 KB. Past roughly 1 MB the performance cost becomes measurable, and sites carrying many megabytes show it as high MySQL CPU and a slow admin. Measure yours with a single SQL query summing the length of autoloaded option values, then find the largest offenders by name before deleting anything.

Is it safe to delete rows from the Action Scheduler tables?

Completed, failed, and cancelled actions are safe to remove. Pending and in progress actions are not, because deleting those cancels real work like a renewal or an expiry check. Take a backup first, delete in batches rather than one large query so you do not lock the table on a live site, and then shorten the retention period so the backlog stops rebuilding itself.

Will switching to HPOS speed up my membership site?

It helps most on sites with a lot of order history, which describes any site running recurring memberships for a couple of years. Dedicated order tables with proper indexes mean fewer reads and writes and less pressure on wp_posts and wp_postmeta. Migrate on staging first, keep compatibility mode on afterwards, and check any plugin that reads order data with custom SQL.

Do I need to move off shared hosting for a membership site?

If you have real members using the site at the same time, usually yes. Shared plans budget PHP workers on the assumption that caching absorbs most traffic, and a membership site breaks that assumption because none of its member traffic is cacheable. Fix the database and caching problems first, since they are free, then move if the logged in numbers still are not acceptable.

Slow for your members, fine for everyone else?

Tell me what your members are experiencing, how many you have, and what plugins are running. I will tell you honestly which of the eight causes above you are hitting and what order to fix them in, whether or not you need anyone to do the work.

Book a Free Strategy Call

Or have the whole thing handled

Database cleanup, caching and object cache setup, HPOS migration, hosting moves, and the ongoing maintenance that keeps a membership site fast after launch week, scoped in writing before anything is touched.

See What I Build