Membership Site RecoveryClient under NDA

Membership Site Stabilized Before Redesign

Bot sign-ups stopped, paying members restored and a hidden block on new sign-ups removed, ahead of a planned redesign

By Lotfi Otmanine

Client
Nonprofit membership organization (anonymized)
Engagement
Fixed-price phases, stabilization phase in progress
Stack
WordPressBuddyBossMemberPressGiveWPStripeCloudflare TurnstileElementorWordfenceQuery Monitor

Key result

200+

bot accounts removed

01

Context

A volunteer-run association runs its member community on WordPress: BuddyBoss for profiles, directory and forums, MemberPress for paid membership levels on Stripe, and GiveWP for donations, on a shared host. The association asked for a redesign. A front-end audit and a read-only admin review showed that the site first had to be made safe and correct: bot accounts outnumbered real members, the public pages were hidden, and paying members were shown as expired. The membership plugin licence had lapsed months earlier, and over a dozen plugins and the core were out of date, some by more than a year.

The work was split into a stabilization phase and a redesign. Every change was rehearsed on a locked-down staging copy, approved in writing, preceded by a fresh full backup on live, and recorded in a change log with its rollback and its verification. Existing subscriptions and the live payment connection were off limits without explicit authorisation, so the payment fixes had to restore members without touching a single subscription.

02

The Challenge

  • Bots were creating accounts every day; roughly four in five user accounts on the site had never belonged to a person.
  • An emergency snippet, added more than a year earlier, hid public pages from visitors and silently blocked every new paid sign-up.
  • Renewals were being charged by the payment processor but never reached the membership plugin, so members who had paid saw their access expire.
  • The login page failed for everyone with PHP memory errors, on shared hosting where the server-level PHP settings cannot be changed from the account.

03

Scope and Approach

Audit and locked-down staging

A front-end audit with desktop and mobile captures, then a read-only admin review of plugins, licences, payments and users. A staging copy on the same host, behind a directory password, with all outgoing email blocked and the membership plugin in payment test mode, so no member could be emailed or charged during tests.

Bot sign-ups and account cleanup

The entry point traced from the access log to the minute each fake account was created. The donation plugin updated and a server-side Cloudflare Turnstile check added to both the donation and registration forms. The deletion rule rehearsed on staging with a before and after comparison, the final count reported, then about 200 bot accounts removed on live after a new backup.

Access and caching

The word-matching snippet replaced by the community plugin's own private-network mode, with an explicit list of public pages and public REST routes. Forum access set by membership level. Two page-cache layers reduced to one, and the visitor cache lifetime cut from an hour to a minute, so members no longer receive a cached "login required" page.

Payment reconciliation and member restoration

Processor payments and subscriptions reconciled against the membership plugin exports into a workbook for the association's treasurer. The missing renewals traced to a disconnected gateway and an expired plugin licence. Access restored by recording each real payment against its existing subscription, with no new charge.

Hardening and content cleanup

Security updates tested on staging, then applied live in batches. An old configuration backup holding the current database credentials moved out of the web root and the database password rotated in a low-traffic window, verified on live and staging. Two-factor sign-in started on an administrator account. Test content replaced with empty-state messages.

04

Architecture flow

Audit + admin review
read-only
Locked-down staging
email blocked, test payments
Fix rehearsed on staging
visitor + member tests
Approved in writing?
yes
Full backup, then live change
rollback ready
no
Held on staging
options sent
Live verification
temporary test members
Change log + findings register
rollback per change

05

Technical Deep Dive

Bot accounts from a donation form

Two bot runs in the access log matched two new accounts to the minute. The host's firewall refused the probe of the plugin's readme, but the bots then listed the donation forms over the public REST API and posted one. The donation plugin creates a user for every submission, completed or not, and this cannot be switched off for that form type. The fix closed both doors: a server-verified Turnstile token, and the form listing removed from the public API.

The sign-up block inside an emergency snippet

The snippet redirected or blanked any address containing words such as "member" or "directory". The paid sign-up address contained one, so visitors were sent to the login form, and the page it redirected members to matched its own rule and looped forever. The last new paid subscription in the processor predates the snippet by two days. Confirmed in the code and on live, then replaced by a deny-by-default allow-list.

Memory errors traced to the opcode cache

Raising the WordPress memory limit ended the login outage, but pages still needed 230 to 260 MB. A diagnostic on staging showed why: one request loads about 4,800 PHP files, while the shared opcode cache holds 4,000 files in 128 MB, was full, and served about a quarter of lookups. Nearly all code was compiled on every request. The host confirmed the setting is server-level, so the raised limit stays a documented stopgap.

A privacy fix that removed the homepage colours

After the private-network change, the homepage lost its brand colours for visitors. The page builder reads its global colours through a REST route, and when a logged-out visit triggered a stylesheet rebuild, the new privacy setting refused that route. The route was added to the public allow-list, and the fix was proven by clearing the builder's files and letting a logged-out visit rebuild them.

06

Engineering Decisions

Rehearse, back up, then change

No live change without a staging rehearsal, written approval, a fresh full backup and a rollback step written beside it in the change log. Verification on live used temporary test members with zero-value memberships, removed afterwards, never a real card payment.

Record the payment that happened

Restoring paid members meant attaching each real processor payment ID to the subscription that already existed, rather than re-charging or rebuilding anything. Rehearsed on one subscription on staging first; on live, every payment ID was checked to appear once, and the subscriptions were left exactly as they were.

Deny by default, list what is public

The snippet guessed what to hide from words in the address, and guessed wrong. The replacement makes the whole community private and lists each public page and REST route by name, so a new page is protected until someone decides otherwise, and each exception is visible in one settings screen.

07

Results

  • Bot account creation stopped at the source on both the donation and registration forms, with token-less submissions refused server-side on staging and live; about 200 bot accounts removed after a staging rehearsal, with no payment record touched.
  • Public pages reopened and the hidden block on new paid sign-ups removed, verified on live as a visitor, as a member without a membership, and at each paid level.
  • Over a dozen members who had paid their renewal regained access with no new charge and no change to their subscriptions, each matched to its processor payment.
  • Login outage ended and its cause measured; an exposed credentials backup removed from the web root and the database password rotated, verified on live and staging.
  • Security updates applied after staging tests, including a donation plugin with published unauthenticated vulnerabilities and a security plugin that crashed while verifying search crawlers.

08

Planned next

  • Close stabilization: restore the membership plugin licence, reconnect the payment gateway so renewals record again, and run final payment tests on live.
  • Roll out first-year pricing through a paid trial, already tested on staging as an alternative to a paid add-on, with returning members charged the renewal price only.
  • Proposed redesign phases, not yet started: a design system and new public pages, a single sign-up flow, directory search and filters, and performance, security header, SEO and accessibility work.

Have a site that needs this kind of care?

Tell me what is breaking or what you need built. I will reply with how I would approach it and what I would check first.

Custom quote within 24 hours

Chat on WhatsApp