Blog · WordPress

Building a Membership Site on WordPress: The Stack That Works

Membership sites are one of the least forgiving WordPress builds. You are not just serving pages, you are serving different pages to different people, taking recurring payments, and trying to keep the whole thing fast even though caching (the thing that makes WordPress fast) is built around the idea that everyone sees the same output. Get the stack right and it runs itself. Get it wrong and you will spend your weekends debugging why paying members see a paywall or why last month’s cache is showing someone else’s dashboard.

The four pieces you actually need

A membership stack has four jobs: gate the content, take the payment, keep the account in good standing, and stay fast without serving the wrong page to the wrong person. Each job maps to a specific tool.

  • A membership or restriction plugin to handle roles, access rules, and drip content.
  • A payment processor with subscription support to handle recurring billing and failed-card recovery.
  • Object caching (Redis or Memcached) to keep logged-in performance reasonable without hammering the database on every request.
  • Full-page cache exclusions so your caching layer never serves a cached member page to someone who has not paid, or a cached free page to someone who has.

Choosing a membership plugin

Most membership plugins fall into two camps: dedicated membership plugins (built specifically for gating content and managing subscriber access) and WooCommerce-based membership extensions (built on top of an existing store). If you already sell physical or digital products through WooCommerce, extending it makes sense, you get one order history, one customer list, one payment flow. If membership is the whole business, a dedicated plugin usually gives you cleaner drip-content scheduling, cohort-style access rules, and simpler reporting without carrying the weight of a full ecommerce cart.

Whichever route you pick, look for three capabilities before anything else:

  • Role-based or level-based access control that works at the post, page, and category level, not just whole-site gating.
  • Drip content scheduling, so new members do not get dumped into three years of archives on day one.
  • A REST API or webhook system, so your payment processor can talk to it directly instead of you reconciling spreadsheets.

Protecting content properly

Hiding a paywalled post behind a shortcode is table stakes, but it is not the whole job. Three things get overlooked constantly:

  • Feeds. RSS feeds will happily serve full post content to anyone unless your plugin explicitly filters them. Check this specifically, do not assume it is covered.
  • The REST API. If your theme or a block editor feature pulls post content through /wp-json/wp/v2/posts, that endpoint needs the same access rules as the front end, or it becomes a quiet back door.
  • Direct media URLs. Gated PDFs, videos, and downloads uploaded to the standard media library are reachable by anyone with the link unless you route them through a protected download handler. Do not rely on “security through obscurity” for anything paid.

You can spot-check what a logged-out visitor sees with wp-cli by pulling the rendered content directly:

wp post get 123 --field=post_content
wp eval 'echo apply_filters( "the_content", get_post_field( "post_content", 123 ) );'

If the second command reveals full gated content, your filter is not running where you think it is.

Payments and dunning

Recurring billing fails for boring reasons: expired cards, banks flagging a repeat charge as suspicious, insufficient funds on the renewal date. This is normal and expected, not a sign something is broken. What matters is how you handle it, a practice generally called dunning: retrying the charge on a schedule, emailing the member with a plain link to update their card, and giving them a grace period before access is actually revoked.

Most subscription-capable payment processors (and most membership plugins that integrate with them) handle the retry logic for you. Your job is to configure it deliberately rather than accept silent defaults:

  • Set a grace period (a few days is typical) before a failed payment downgrades access, so one declined card does not instantly lock someone out.
  • Make sure the “update your card” email actually reaches the member, check it is not landing in spam, and that the link goes to a working, mobile-friendly billing portal.
  • Log every subscription state change (active, past due, canceled) somewhere you can query it. When a member emails asking why they lost access, you want an answer in seconds, not an afternoon in the payment processor’s dashboard.

The caching exceptions members need

This is where membership sites most often go wrong. Full-page caching (LiteSpeed Cache paired with a LiteSpeed or OpenLiteSpeed server is the standard combination here) is designed around the assumption that a given URL returns the same HTML to everyone. Membership content breaks that assumption constantly. The fix is not to disable caching, it is to scope it correctly:

  • Never cache logged-in requests as a public page. LiteSpeed Cache and similar plugins detect logged-in sessions automatically and serve a private, per-user cache instead of the shared public cache. Confirm this is actually working rather than assuming it.
  • Exclude account and billing pages entirely. Dashboard, billing history, and “update payment method” pages should be marked no-cache. These pages change per request and caching them risks showing one member’s billing details to another.
  • Use ESI (Edge Side Includes) for member-specific fragments on otherwise cacheable pages, like a “welcome back, Jane” widget on a public landing page. This lets the bulk of the page stay fully cached while the personalized sliver renders fresh.
  • Lean on Redis or Memcached for object caching instead of trying to force full-page caching to do a job it is not built for. Persistent object caching (via a drop-in) speeds up the database queries that power access checks and account lookups, which is where logged-in performance actually lives.

If you are running Cloudflare in front of the site, keep the same discipline at the edge: exclude cookies-present requests and account paths from edge caching, and let Cloudflare’s WAF and bot mitigation do what they are good at, filtering junk traffic before it reaches your origin, rather than trying to cache logged-in HTML there too. Managed hosts that specialize in WordPress, ServerBorn included, generally handle this LiteSpeed-plus-Redis-plus-Cloudflare coordination as part of the stack, which removes one whole category of “why did caching break my paywall” tickets.

Performance basics that still apply

Membership sites are still WordPress sites, and the fundamentals do not change. Run PHP 8.3 with OPcache enabled, it is the current solid default and gives a meaningful speed floor before you touch anything else. Keep an eye on Core Web Vitals even on gated pages, since members judge your site by how it feels: LCP at 2.5 seconds or under, INP at 200 milliseconds or under, and CLS at 0.1 or under. A slow member dashboard is a churn risk exactly like a slow checkout page.

The takeaway

A membership site succeeds or fails on details most visitors never see: whether feeds leak gated content, whether a failed card gets a grace period instead of an instant lockout, and whether your cache knows the difference between a public page and a private one. Pick a plugin that fits your business model, configure dunning deliberately instead of trusting defaults, and scope your caching layer with intention. Get those three right and the stack fades into the background, which is exactly where you want it.