Skip to content
All case studies
case studyVic Dorfman

A 30,000-contact email list kept crashing a membership site. Here's what broke.

Pharmacy Badass University, a membership for independent pharmacy owners, kept going down when its emails went out. What MemberFix found on the old Cloudways server, what we fixed there, and what changed after the move to MemberHost.

Listen to this articleAI narration in Vic Dorfman's cloned voice

DiversifyRx runs Pharmacy Badass University, a paid membership for independent pharmacy owners. It sells subscriptions through WooCommerce, teaches through LearnDash, and emails about 30,000 contacts through FluentCRM several times most weeks.

In August 2026 the site kept going down, and it tended to go down right after the emails went out. MemberFix, our sister agency, spent August finding out why and fixing what it could on the old Cloudways server. On 12 September we moved the site to MemberHost.

Written by Vic Dorfman, co-founder of MemberHost and founder of MemberFix. The work was done by Trim at MemberFix and Risto Karaivanov at MemberHost. Figures come from the two servers’ logs and FluentCRM’s own records.

Crashes from running out of memory
3 to 0
3 on the old server, out of 9 reboots from 3 Aug to 9 Sep. None on MemberHost, 12 to 18 Sep.
Days with processes killed for lack of memory
11 to 0
11 of the 25 days the old server's memory log covered. 0 on MemberHost, 12 to 18 Sep.
Server time to build the homepage
3.6 s to 1.6 s
Uncached, median of repeated runs, network time removed.
Emails sent in the first four days
113,327
13 to 16 Sep, the last day partial. 3 failed.

The site

  • Client: DiversifyRx, for Pharmacy Badass University
  • What it is: a paid membership, courses and community for independent pharmacy owners
  • Size: 523 member accounts, 273 active subscriptions, about 30,000 email contacts
  • Plugins that matter here: WooCommerce Subscriptions, LearnDash, FluentCRM and Elementor, among 90+
  • Before: Cloudways Autonomous, the plan that scales with traffic; the server we measured had 4 vCPU and 16 GB of memory
  • After: MemberHost, since 12 September 2026
  • Who did the work: MemberFix (diagnosis, fixes, migration) and MemberHost (hosting)

Five weeks on the old server

The old server rebooted 9 times between 3 August and 9 September. Three of those reboots came straight after the server ran out of memory. Its memory log only reached back to 18 August, and from then until the move, processes were killed for lack of memory on 11 separate days: 13 of them on 9 September alone.

Members felt it. On 14 August a newsletter to 30,441 people stalled at 83% while the server fought for memory. Subscription renewals had quietly stopped going through (more on that below).

Reboots and out-of-memory kills, day by dayReboots on the old Cloudways server, 3 August to 11 September. Out-of-memory kills on both servers, with MemberHost from 12 September.
Between 3 August and 11 September the old server rebooted 9 times, 3 of them after running out of memory, and processes were killed for lack of memory on 11 of the 25 days the memory log covers, with 13 on 9 September. From 12 to 18 September on MemberHost there were no out-of-memory kills. Reboots are shown for the old server only. The table below the chart lists every day.No memory logkept before 18 Aug0510Reboots(old server)Killedper dayMoved toMemberHost13 kills that day~14, seen live0 kills3 Aug10 Aug17 Aug24 Aug31 Aug7 Sept14 Sept

Scroll sideways for the rest of August.

Show the data as a table
DayWhat happened
3 Augno memory log kept for this day. server rebooted.
6 Augno memory log kept for this day. server rebooted.
11 Augno memory log kept for this day. server rebooted.
12 Augno memory log kept for this day. server rebooted.
13 Augno memory log kept for this day. server rebooted.
14 Augabout 14 processes killed (seen live, log since rotated). server rebooted.
18 Aug2 processes killed for lack of memory. server rebooted after running out of memory.
20 Aug2 processes killed for lack of memory. server rebooted after running out of memory.
28 Aug2 processes killed for lack of memory.
29 Aug1 processes killed for lack of memory.
1 Sept5 processes killed for lack of memory.
2 Sept1 processes killed for lack of memory.
3 Sept1 processes killed for lack of memory.
8 Sept2 processes killed for lack of memory.
9 Sept13 processes killed for lack of memory. server rebooted after running out of memory.
10 Sept1 processes killed for lack of memory.
11 Sept4 processes killed for lack of memory.
12 Sept0 processes killed for lack of memory. site moved to MemberHost.

Days not listed had no reboot and, from 18 August on, no out-of-memory kills.

One crash, minute by minute

The 20 August crash shows how little it took. Times are the server’s own.

Illustration: a swarm of small crawler drones converging on a shopping cart and a coupon while the server behind them overheats
  1. 15:00 A “Member Mastermind” email goes out.
  2. 18:27 In about 35 seconds, around 20 Facebook link-preview crawlers fetch a coupon link and the cart page. At the same time, an automated tool that turns web pages into PDFs loads 80 of the email’s tracking pixels.
  3. 18:30 PHP goes from 1 process to 146, holding about 15 GB of the server’s 16 GB.
  4. 18:30 to 18:55 Out of memory, the server swaps to disk and freezes for about 25 minutes.
  5. About 18:50 The system kills two processes to free memory.
  6. 18:54 The server reboots.

Crawlers and one PDF tool, fetching pages that had to be built fresh, took the site down for about half an hour.

What was really going on

There was no single cause. Four problems were stacked on top of each other, and each one made the others worse.

Every email send pulled a crowd the page cache couldn’t help with. The page cache worked: anonymous visits to the homepage were served from it. But an email to 30,000 people triggers opens, clicks, security scanners that check every link, and link previews on social media, and a lot of that traffic lands on pages that can’t be cached, like the cart, coupon links and the email’s own tracking links. Each of those is a full page build in PHP. FluentCRM was also sending faster than its own per-second limit allowed until an update fixed it.

Renewals were stuck, and the stuck queue was hammering the site. WooCommerce Subscriptions has a “Dedicated Processing” option that rotates renewals through their own queue. Its rotation counter was stored in the database and cached in Redis, and the two copies fell out of step. The queue logged “rotation turn 2 of 3” 8,585 times and never ran a renewal. Meanwhile it kept calling the site to try again: 6,130 of those calls in one day, all from the server to itself, each tying up a PHP process. MemberFix switched Dedicated Processing off on 16 August. 18 subscription payments went through that day, about 13 of them the stuck backlog, with no failures, and the server’s load dropped from about 15 to about 2.

Scheduled jobs piled on top of each other. Cloudways ran WordPress’s scheduled jobs every five minutes with nothing to stop a new run starting while the last one was still going. On 18 August five were running at once. On 19 August MemberFix replaced it with a server cron job that won’t start a second copy.

Editing a page burned PHP processes. Three unused experimental features in Elementor 4 fired about 27 background requests a minute whenever the editor was open. MemberFix switched them off on 12 August.

One missing setting turned all that into crashes: nothing capped the number of PHP processes. When a burst arrived, PHP started a process for every request, each one holding its share of memory, until there was none left. Try it with your own numbers:

What would a burst of uncached requests do to your server?

The numbers filled in are an example, not this client's. Put in your own.

-

The check needs JavaScript to calculate.

requests arrive evenly over the burst and each holds a process for the PHP time; the peak count of processes running at once x memory per process = memory needed

A rough model, and a kind one. It assumes the requests arrive evenly and each takes the same time. When memory runs out the server starts swapping to disk, every request slows down, and more of them pile up, so an uncapped burst usually ends worse than this shows. A cap can't make the demand go away: requests over it wait their turn, and if they wait too long some can time out. A cap set higher than your memory allows doesn't protect you either.

Why we moved it

The August fixes stopped the self-inflicted load. The server didn’t reboot again until 9 September. The cap on PHP processes was the fix still missing: it was recommended, and it was never applied on that server. So bursts kept costing processes: 28 and 29 August, 1 to 3 September, 8 September, then 9 September, when 13 processes were killed and the server rebooted once more, and 10 and 11 September, when more processes were killed, right up to the move.

The MemberHost setup deals with that directly:

  • A cap on PHP. PHP runs with a fixed limit on processes, sized for the server’s memory, so a burst mostly waits in line instead of eating the memory.
  • Scheduled jobs outside the web server, as server cron jobs that can’t overlap, one for WordPress’s jobs and one for the background queue.
  • More CPU, on the site’s own node with dedicated resources.
  • A simpler server stack. LiteSpeed serves and caches the pages, tuned for this site, where the old server chained nginx, Apache and Varnish.

The move, 12 September

The database was 21.7 GB. Almost none of that was member data.

What was in the 21.7 GB databaseGigabytes. Only the part on the right came across.
Email send log 13.3 GB
333,772 rows, each with the full message body
Campaign tracking 3.9 GB
6.0 million per-recipient rows
Everything else left behind 3.3 GB
the remainder, not measured on its own; includes 415 tables from an old staging copy of the site
Moved to MemberHost 1.2 GB
members, orders, subscriptions, courses and contacts

Every member, order, subscription, course and contact came across. The email send log, the per-recipient campaign history and the leftover staging tables stayed on the old server. The site files went from 23 GB to 6.1 GB the same way: 15 GB of uploads belonged to a staging copy, and 2.3 GB were old backups.

Moving a site that bills people and emails 30,000 of them needs care at the switchover:

  • No orders lost. The old site was frozen, checked for changes and synced again. 4 orders placed during the move came across.
  • No double emails. Two scheduled promotions were set to go out from both servers. Left alone, every contact would have got each one twice.
  • Guarding against double charges. The old server’s scheduled jobs, background queue and outgoing email were switched off, so it couldn’t bill anyone or send anything.

The old Cloudways setup’s caching had problems of its own, which we fixed as part of the move:

  • The login page was cached for 7 days, security token included. Login, checkout, cart and account pages, 12 paths in all, are now kept out of the cache.
  • A cached redirect locked members out. A security plugin’s lockout page pointed at an address that only exists on the server itself, and one cached copy of that redirect sent everyone visiting the login page there. Cleared and repointed.
  • Error pages were cached for 10 minutes, which turns a brief failure into a long one. Not any more.

And a few in the site itself:

  • Background jobs could stall for an hour. A LearnDash setting forced the background queue’s timeout to 3,600 seconds with a single worker, so one stuck job held up renewals and campaigns behind it. There were 13 hour-long stalls on record. It’s now 300 seconds, with 3 batches in parallel.
  • Bulk campaign logging is off. Storing every campaign email in full added about 1.2 GB per send. Transactional email logging stays on.
  • The homepage hero lost its layout to an Elementor 4.2.4 bug. Rolled back to 4.2.3.

The results so far

The homepage now takes the server 1.6 seconds to build from scratch, down from 3.6.

One uncached homepage request, before and afterSeconds, median of repeated runs from a test machine in Europe
  • Connect
  • Secure connection (TLS)
  • Waiting for the server
  • Downloading the HTML

Before Cloudways, New Jersey

Server work: 3.6 s

After MemberHost, Oregon

Server work: 1.6 s

"Server work" is the wait minus one network round trip (0.13 s before, 0.26 s after). The new server is farther from the test machine, which is why connecting got slower, so the real server gain is if anything a little larger than shown. One page, one visitor at a time: this is not a load test.

The bigger change is stability. From 12 to 18 September the new server killed no processes for lack of memory and had no out-of-memory crash. On 18 September it had 10 GB of memory available and none of its swap in use.

The emails kept going out. From 13 to 16 September (the last day partial) the site sent 113,327 emails. 3 failed and 105 were canceled. That’s about 28,000 a day, the same pace as the old server’s busiest full weeks. When we checked in mid-September, the background queue had nothing overdue.

These are early numbers. A week is a short window, and we’ll update this page as the months go by.

When a lot of people show up at once

For this site, the rushes come from email sends: tracking links, link scanners and social previews, many of them for pages that can’t be cached. One of those bursts, on 20 August, is what took the old server down. Launches, live sessions and waves of new sign-ups create the same kind of rush on most membership sites, though we haven’t tested those on this one yet.

Here’s how this site is set up for it on MemberHost:

  • Cached pages are served without PHP. When a page is in LiteSpeed’s cache, “the web app does not need to regenerate the page” (LiteSpeed). Visitors landing on cached pages cost the server very little. The cart, checkout, tracking links and logged-in pages still need PHP.
  • PHP has a ceiling. With a cap on PHP processes, a rush of uncached requests waits for a free process instead of each one starting a new process until the memory runs out. The calculator above shows that trade.
  • More CPU. More cores let more of those PHP requests run side by side.

In its first week the new server killed no processes for lack of memory (12 to 18 September), while sending emails at about the pace of the busiest weeks on Cloudways (13 to 16 September).

What we haven’t measured yet is how many members can be logged in and clicking at the same moment before pages slow down. That needs a load test, and we’d rather run it against a copy of the site than against the client’s members. We’ll add the numbers here when we have them.

What we haven’t used yet

There’s headroom we’ve measured and not switched on:

  • A persistent object cache. With one enabled in testing, the new server built the homepage in 0.7 to 0.9 seconds instead of 1.3 to 1.7. It isn’t switched on yet.
  • Fewer, smaller files. The homepage loads 70 stylesheets and 56 scripts. Combining and minifying them should cut the requests every visitor makes. We’ll measure it before and after.
  • Edge caching. Serving cached pages from Cloudflare’s network would put them closer to members on the East Coast. The server is in Oregon.

What to take from this if you run a membership site

  • Cap your PHP processes. Without a cap, a big enough burst of uncached requests can use up every byte of memory. With a cap sized to your memory, the same burst mostly means a slower site while the queue clears.
  • Watch the hour after a big send. Your page cache won’t cover cart, coupon or tracking links, and scanners and link previews hit them fast.
  • Make sure scheduled jobs can’t overlap. Five overlapping runs helped bring this server down on 18 August.
  • Be careful with object caching and renewals. A stale value in Redis stopped renewals here without a single error message.
  • Don’t keep logs in your database. About 17 of the 21.7 GB here was email history.

How we measured

  • Speed: the homepage only, with a unique query string on every request so it skipped the page cache, 3 to 8 runs per setup, medians. Server time is the wait for the first byte of the page minus one network round trip, cross-checked by measuring directly on the new server. Cached pages were near-instant on both servers, so we didn’t compare them. Logged-in member pages weren’t measured separately, and this was not a load test.
  • Reboots and memory: reboot times from the old server’s boot log. Processes killed for lack of memory from its system activity log, which kept about 31 days, so it starts on 18 August. The 14 August figure was seen in a live session before that day’s log was rotated. New server figures are from its logs for 12 to 16 September and from MemberFix’s health check on 18 September.
  • Email: FluentCRM’s own send records on each server.

Frequently Asked Questions

Why did the site crash when it wasn’t busy?

Much of the load came from the site’s own background queue calling itself thousands of times a day, scheduled jobs running on top of each other, and bots that check links in emails. All of it needs PHP.

Would more memory on Cloudways have fixed it?

More memory would have raised the ceiling, but without a cap on PHP processes a big enough burst still uses it all. The fixes that mattered were the renewal queue, the overlapping jobs and the cap, and the move is where the cap was applied.

Did any member data get left behind?

No. Every member, order, subscription, course and contact came across. What stayed on the old server was the email send log, per-recipient campaign history and an old staging copy.

Is the site faster for logged-in members?

We haven’t measured member pages separately yet, so we won’t claim it. We measured the homepage, uncached, one request at a time.

Ultra performant WordPress hosting for your membership site

Your site has unique technical needs: lots of heavy plugins, hundreds of concurrent users, traffic spikes. Big box hosting companies do not even know about these needs, let alone how to address them properly. But we do.