Category: Hosting

  • WordPress plugin memory limits: when 256 MB isn’t enough

    WordPress plugin memory limits: when 256 MB isn’t enough

    WordPress plugin memory limits: when 256 MB isn't enough
    Photo: Simone Bignetti via Wikimedia Commons (CC BY-SA 4.0)

    Your WordPress dashboard throws a white screen. The error log says “Allowed memory size exhausted.” You’re on a plan with 256 MB of PHP memory—more than most blogs need. So what’s using it all?

    Memory limits aren’t storage. They’re the ceiling for how much RAM a single PHP process can consume while executing a request. When a plugin loads data, processes images, or runs a bulk operation, it eats into that budget. Cross the line and PHP kills the request.

    Most hosts default to 128 MB or 256 MB. That works fine until it doesn’t. Here’s when plugins push past the standard ceiling, what triggers the spike, and how to fix it without upgrading your whole server.

    What actually consumes plugin memory

    Memory spikes happen when plugins hold large datasets in RAM instead of querying the database in smaller chunks. The most common culprits:

    • Image optimization plugins processing high-resolution uploads—especially batch operations that resize 50+ images at once
    • Export tools generating CSV files from thousands of rows without pagination
    • SEO crawlers building internal link graphs across hundreds of posts in a single pass
    • Page builders loading every block, widget, and custom field into memory when rendering complex layouts
    • Backup plugins compressing full-site archives without streaming to disk

    A single 8,000-pixel JPEG can temporarily consume 180 MB during processing. If your image optimizer batches five at once, you’ve blown past 256 MB before the first thumbnail renders.

    The deceptive part: memory use is per-request, not per-plugin

    You can’t see cumulative memory use in a dashboard widget. PHP allocates memory when a request starts and releases it when the request ends. A plugin that uses 40 MB on one page might use 300 MB on another, depending on what data it loads.

    That’s why your site runs fine most of the time, then crashes during a specific action—importing a batch of products, regenerating thumbnails, or exporting a year of form submissions. The plugin isn’t broken. It’s trying to process more data than your memory ceiling allows in a single execution.

    Check your server’s error log (usually in wp-content/debug.log or your host’s control panel). Look for lines containing Allowed memory size of X bytes exhausted. The timestamp tells you which request triggered it. Match that to your activity log and you’ll find the operation that spiked.

    How to raise the limit (and when not to)

    You can increase PHP memory in three places, listed by override priority:

    1. wp-config.php
    Add this line above the “stop editing” comment:
    define('WP_MEMORY_LIMIT', '512M');

    This sets the ceiling for front-end and dashboard requests. Some hosts ignore it if their server-level cap is lower.

    2. php.ini or .user.ini
    If your host gives you access, set:
    memory_limit = 512M

    This applies to all PHP scripts, not just WordPress. Shared hosts often lock this file.

    3. Host control panel
    Most managed WordPress hosts let you adjust PHP memory via a slider or dropdown. BigScoots and similar performance-focused hosts typically allow 512 MB or 1 GB on mid-tier plans.

    Before you raise the limit, confirm the spike is legitimate. If a lightweight contact form is consuming 400 MB, the plugin is likely inefficient or conflicting with another script. Throwing more RAM at bad code just delays the next crash.

    When to re-architect instead of raising the ceiling

    If you’re hitting memory limits on routine operations—publishing a post, loading the media library, saving a page—something’s wrong with your stack. Raising the limit to 1 GB might make the error disappear, but you’re masking a structural problem.

    Consider:

    • Switching to chunked processing. Many backup and image plugins offer “process in batches of 10” settings. Turn them on.
    • Offloading heavy tasks to cron jobs. Instead of regenerating 500 thumbnails in one click, schedule the plugin to process 50 every hour overnight.
    • Using a dedicated service for resource-intensive jobs. Image optimization works better when handled by an external API like ShortPixel or Imagify, which never touch your server’s memory.
    • Profiling which plugin is the hog. Deactivate half your plugins, test the operation, then narrow it down. Replace the offender if possible.

    Memory limits exist to prevent a single runaway script from crashing your entire server. If your site genuinely needs 1 GB per request, you’ve outgrown shared hosting and should move to a VPS where you control resource allocation per process.

    Want more infrastructure breakdowns like this? Subscribe to One Two Three Send for one operator-focused article every morning—no fluff, no affiliate spam, just the mechanics of running an online business.

  • WordPress cache plugins flush differently—and it costs you traffic

    WordPress cache plugins flush differently—and it costs you traffic

    WordPress cache plugins flush differently—and it costs you traffic
    Photo by Justin Morgan on Unsplash

    You hit publish on a post, flush your cache, and check the front end. Half the time, the old version is still there. Or worse: the homepage updated but the category archive didn’t. Or the mobile cache cleared but desktop didn’t.

    Most solo operators assume cache plugins work the same way. They don’t. The three most popular WordPress caching solutions—WP Rocket, W3 Total Cache, and LiteSpeed Cache—handle purge logic, CDN sync, and object invalidation completely differently. And if you’re running a content site where every minute of stale cache costs you click-through or ad impressions, those differences matter.

    Here’s what actually happens when you click “clear cache,” broken down by plugin.

    WP Rocket: instant page cache, delayed object cache

    WP Rocket clears page cache immediately when you publish or update a post. That means the single post URL, homepage, and any archive that post appears on get purged within seconds.

    But if you’re using Redis or Memcached for object caching, WP Rocket doesn’t touch it. Object cache stores database query results, widget output, and navigation menus. If you update a menu item or change a widget, the page cache clears but the object cache doesn’t—so your navigation still shows the old link until the object cache TTL expires (usually 12–24 hours by default).

    The workaround: manually flush object cache from your hosting dashboard or install a separate plugin like Redis Object Cache and set a shorter TTL (3–6 hours). WP Rocket won’t do it for you.

    WP Rocket also integrates with Cloudflare’s API to purge CDN cache automatically. It works, but there’s a 30–90 second delay between WordPress purge and Cloudflare purge. If you’re checking your site immediately after publish, you’ll see stale content. Wait two minutes, or purge Cloudflare manually from their dashboard.

    W3 Total Cache: granular control, manual overhead

    W3 Total Cache gives you separate toggles for page cache, object cache, database cache, browser cache, and CDN. That’s powerful, but it also means nothing clears unless you’ve configured it to.

    By default, publishing a post clears that post’s page cache and the homepage. It does not clear category archives, tag archives, or related post widgets unless you enable “purge all caches on post publish” in settings. That setting works, but it’s overkill—it nukes the entire page cache, including evergreen pages that didn’t change.

    Better approach: enable selective purge for post archives, categories, and tags. You’ll find this under Performance > Page Cache > Purge Policy. Check the boxes for “front page,” “post page,” “categories,” and “tags.” Skip “all pages” unless you’re running a small site (under 100 pages).

    For object cache, W3 Total Cache will clear Redis or Memcached—but only if you enable “flush object cache on post publish” in the same purge policy menu. It’s off by default.

    CDN purge is manual unless you’re using a supported CDN like Cloudflare, StackPath, or BunnyCDN. You’ll need to enter API credentials under Performance > CDN and enable automatic purge. Even then, expect 60–120 second lag.

    LiteSpeed Cache: instant everywhere, but only on LiteSpeed servers

    LiteSpeed Cache is the fastest of the three—if you’re on LiteSpeed web server hosting (like BigScoots, Hostinger, or A2 Hosting). It uses server-level cache instead of PHP-based cache, so purges happen in under five seconds.

    When you publish a post, LiteSpeed Cache clears the post itself, homepage, relevant archives, and any widget areas that reference the post. It also purges object cache (if enabled) and triggers CDN purge via API (Cloudflare and Quic.cloud supported).

    The catch: it only works on LiteSpeed servers. If you’re on NGINX or Apache, the plugin installs but doesn’t cache anything. You’ll see “LiteSpeed Cache is not running” in the dashboard. In that case, switch to WP Rocket or W3 Total Cache.

    One non-obvious LiteSpeed quirk: mobile cache is separate from desktop cache. If you enable mobile-specific caching (recommended for ad-heavy sites), you need to manually flush mobile cache from the LiteSpeed dashboard. It won’t auto-purge on post publish unless you enable “purge all on publish,” which—again—is overkill.

    What to do if cache won’t clear

    If you’ve flushed cache and the old version persists, check these in order:

    • Browser cache. Hard refresh (Cmd+Shift+R on Mac, Ctrl+Shift+R on Windows). Your browser caches pages locally even if server cache is clear.
    • CDN cache. Log into Cloudflare, BunnyCDN, or your CDN dashboard and purge manually. Most cache plugins don’t purge CDN by default.
    • Object cache TTL. If Redis or Memcached is enabled, check your TTL setting. Anything over 12 hours is risky for frequently updated sites.
    • Hosting-level cache. Some hosts (WP Engine, Kinsta, Flywheel) run their own server cache layer that sits above your plugin. You need to purge it from their dashboard or WP Admin bar.

    Which plugin to use

    If you’re on LiteSpeed hosting: use LiteSpeed Cache. Nothing else is faster.

    If you’re on shared hosting or VPS with Apache/NGINX and want zero config: use WP Rocket. It’s $59/year, but it works out of the box.

    If you want granular control and don’t mind spending 20 minutes in settings: use W3 Total Cache. It’s free, and once configured, it’s rock-solid.

    Whatever you pick, test it. Publish a draft post, flush cache, and check the homepage, category page, and post URL on desktop and mobile. If any of them show stale content after two minutes, your purge logic isn’t working.

    Got a caching quirk that’s costing you traffic? Reply to this email—I’ll dig into it for a future piece.

  • WordPress admin user roles expose more data than you think

    WordPress admin user roles expose more data than you think

    WordPress admin user roles expose more data than you think
    Photo: Matinbeigi via Wikimedia Commons (CC BY-SA 4.0)

    Most solo operators running WordPress sites hand out Editor or Author accounts without thinking twice. A guest contributor needs to draft a post. A VA needs to schedule content. A designer needs to tweak a page. You create the account, pick a role from the dropdown, and move on.

    But WordPress user roles aren’t just about what someone can publish—they control what someone can see. And the default permissions expose more than most site owners realize.

    What each role actually accesses

    WordPress ships with five default roles. Here’s what each one can see and do, with the gaps that catch people off guard:

    Administrator: Full control. Installs plugins, changes themes, deletes users, accesses all post types, sees all custom fields. No surprises here.

    Editor: Can publish and manage all posts and pages—including other users’ drafts. Can moderate comments. Can see private posts. Cannot install plugins or change site settings. But here’s the catch: Editors can see all content across the site, including unpublished work from other authors. If you’re working on a product launch post in draft mode, your Editor can read it.

    Author: Can write, edit, and publish their own posts. Can upload media. Cannot see other users’ drafts or private posts. Cannot edit pages. This is the role most operators think they’re assigning when they want someone to “just write posts.” But Authors can still delete their own published posts after the fact, and they can upload files to your media library with no size or type restrictions unless you add a plugin to lock it down.

    Contributor: Can write and edit their own posts, but cannot publish them. Cannot upload files. Drafts stay in draft mode until an Editor or Admin publishes them. This is the safest role for guest writers, but it adds friction—every post needs manual review and publishing.

    Subscriber: Can log in and manage their own profile. That’s it. This role exists mostly for membership sites or comment systems that require login. It doesn’t grant access to the WordPress admin dashboard in any meaningful way.

    Where exposure happens in practice

    The most common leak: Editors seeing subscriber data.

    If you’re running a membership site or using a plugin like MemberPress, Restrict Content Pro, or Paid Memberships Pro, Editors can often view the full subscriber list—names, emails, membership levels, and payment history. The plugins don’t always respect WordPress’s user role boundaries. They assume anyone with Editor-level access is trusted with customer data.

    Same problem with form plugins. If you’re collecting emails via WPForms, Gravity Forms, or Fluent Forms, an Editor can usually export the entire entry database as a CSV. That means every lead magnet signup, every contact form submission, every quiz result.

    Another gap: Authors seeing plugin settings they shouldn’t touch.

    Some plugins—especially SEO tools like Yoast or RankMath—let Authors edit meta descriptions and schema markup on their own posts. That’s fine. But if the plugin doesn’t scope its settings properly, Authors might also see site-wide SEO settings, analytics tokens, or API keys in the plugin’s dashboard. They can’t change them, but they can see them. If you’ve pasted a Google Analytics measurement ID or a Mailchimp API key into a plugin settings page, check whether your Author-level users can view it.

    How to audit what your users can see

    Log out of your admin account. Create a test user for each role you’ve assigned to real people—Editor, Author, Contributor. Log in as each test user and click through the dashboard.

    Check these areas specifically:

    • Posts → All Posts: Can they see drafts from other users?
    • Users → All Users: Can they see the subscriber list and export emails?
    • Plugin settings pages: Can they view API keys or third-party credentials?
    • Media library: Can they see all uploaded files, or just their own?
    • Custom post types (if you use them): Can they access posts they shouldn’t edit?

    If you find something you don’t like, you have two options: switch to a more restrictive role, or install a role management plugin like PublishPress Capabilities or Members. Both let you fine-tune permissions at the capability level—”edit_others_posts,” “view_users,” “upload_files”—without writing code.

    One non-obvious fix

    If you need someone to publish posts but not see other users’ drafts, create a custom role. Use PublishPress Capabilities or Members, duplicate the Author role, and add the “publish_posts” capability. Name it something like “Publisher.” This gives them the ability to publish their own work without exposing them to the full Posts list that Editors see.

    For most solo operators, this setup works better than Editor access. You avoid the draft-exposure problem, and you keep tighter control over who sees subscriber data.

    Before you hand out the next Editor account, log in as that role and see what you’re actually sharing. It’s more than you think.

    Got a WordPress question you’d like answered? Reply to this email—we cover operator problems every week.

  • Ghost CDN image transforms: what resizes, what doesn’t, and cost

    Ghost CDN image transforms: what resizes, what doesn’t, and cost

    Ghost CDN image transforms: what resizes, what doesn't, and cost
    Photo: TMM53 via Wikimedia Commons (CC BY 4.0)

    Ghost ships with a content delivery network that automatically resizes images. Upload a 4000×3000 PNG, and Ghost serves readers a 720px WebP instead—unless your theme requests a different size, or the image format doesn’t support transformation, or you hit one of several quiet edge cases.

    Most operators assume the CDN “just works.” It mostly does. But understanding what triggers a transform, what gets served as-is, and how Ghost bills for bandwidth saves you from surprise overage invoices and layout shifts when images don’t resize as expected.

    What formats transform and what gets passed through

    Ghost’s CDN—hosted on Cloudflare for Ghost(Pro) plans and configurable for self-hosted installs—applies transformations to JPEG, PNG, and WebP uploads. It converts them to WebP or AVIF (depending on browser support) and resizes them based on the srcset attributes your theme declares.

    GIF files do not transform. Neither do SVGs. Upload a 2MB GIF, and every reader downloads the full 2MB, regardless of viewport size. Same with SVG: no resize, no format conversion. Ghost serves them byte-for-byte as uploaded.

    This matters if you’re embedding animated illustrations or icon libraries. A single large GIF in a popular post can chew through your monthly bandwidth allocation in hours. Check your Ghost analytics bandwidth report (Settings → Labs → Beta features → Enable staff users) to see per-post transfer totals. If one post accounts for 40% of your monthly bandwidth, check for un-transformed assets.

    Resize breakpoints and theme control

    Ghost doesn’t decide image sizes on its own. Your theme’s {{img_url}} helper or <img srcset> markup tells the CDN which widths to generate. The default Casper theme requests 300w, 600w, 1000w, and 2000w variants. If your theme doesn’t declare a srcset, Ghost serves the original upload—no resize.

    Custom themes sometimes hardcode a single image URL without size parameters. When that happens, readers on mobile download the same 2000px file as desktop users. You’ll see this in your bandwidth logs: high transfer volumes from mobile user agents.

    The fix: audit your theme’s image partials. Look for <img src="{{img_url}}"> without a size argument or srcset. Replace with <img srcset="{{img_url size='s'}} 300w, {{img_url size='m'}} 600w, {{img_url size='l'}} 1000w, {{img_url size='xl'}} 2000w" sizes="(max-width: 800px) 400px, 800px">. Ghost’s CDN generates those variants on-demand and caches them.

    What you pay and when costs spike

    Ghost(Pro) plans include bandwidth: 50 GB on the Starter plan ($11/month), 350 GB on Creator ($31/month), and 1 TB on Team ($63/month). Overages cost $1 per GB on Starter, $0.50 per GB on Creator, and negotiated rates on Team.

    Image transforms themselves don’t cost extra—they’re included in the CDN—but cache misses drive up bandwidth. Each new transform (a new size or format combination) counts as a fresh origin request and CDN delivery. If your theme declares ten srcset widths and your CDN cache TTL is short, you’re generating and serving ten variants per image per cache expiration cycle.

    Ghost’s CDN cache is tied to Cloudflare’s edge network. Time-to-live defaults to one year for transformed images, but purging your CDN cache (via Ghost admin or API) invalidates all variants. If you purge after every post publish, you’re forcing full re-transforms for your entire image library every time. That’s fine for a 20-post blog. It’s expensive for a 2,000-post archive.

    One operator I talked to in August saw bandwidth jump from 80 GB to 240 GB in a single month after switching themes. The new theme requested five additional srcset widths. Ghost generated five new variants for every image in every post ever published, then served all of them to returning readers whose browsers requested the new sizes. Cost: $80 in overage fees on a Creator plan.

    One non-obvious tip: preload hero images with explicit dimensions

    Ghost’s image CDN is reactive. It doesn’t pre-generate transforms until a reader requests a specific size. If your post’s hero image is above the fold and your theme doesn’t preload it, readers see layout shift while the browser waits for the CDN to resize and deliver the image.

    Fix it by adding explicit width and height attributes to hero images in your theme’s post template. Ghost’s CDN still transforms the image, but the browser reserves layout space immediately. Add <link rel="preload" as="image" href="{{img_url feature_image size='xl'}}"> in your post head partial for above-fold images. The CDN delivers the transform faster, and Core Web Vitals improve.

    Most operators never touch Ghost’s CDN settings. But if you’re seeing unexpected bandwidth bills, slow image loads, or layout shifts on mobile, start by checking which formats you’re uploading, what your theme requests, and whether your cache purge strategy is forcing expensive re-transforms.

    Got a Ghost CDN question or a bandwidth story? Reply to this email—I read every one, and operator experiences often become future articles.

  • WordPress plugin auto-updates: when to enable and when to wait

    WordPress plugin auto-updates: when to enable and when to wait

    WordPress plugin auto-updates: when to enable and when to wait
    Photo by Justin Morgan on Unsplash

    WordPress introduced per-plugin auto-update toggles in 5.5, and most operators treat them like a binary choice: turn them all on for convenience, or leave them all off out of caution. Both approaches miss the middle ground where you actually save time without risking downtime.

    The feature works simply—WordPress checks twice daily for plugin updates, and if auto-update is enabled for a given plugin, it installs the new version without asking. No dashboard visit required. But the consequences of a bad update vary wildly depending on which plugin breaks.

    What breaks when a plugin auto-updates badly

    Not all plugin failures are equal. A broken SEO plugin might quietly stop rendering meta tags—you won’t notice for weeks. A broken caching plugin can return 500 errors to every visitor within seconds.

    The worst auto-update failures happen with plugins that touch three areas: authentication, caching, and database schema. If a security plugin misconfigures after an update, you can lock yourself out. If a caching plugin throws a PHP fatal error, your site goes down. If a form plugin botches a database migration, you lose submissions.

    Plugins that only add admin-side functionality—like editorial workflow tools or media library organizers—carry much less risk. If they break, your front-end keeps running, and you can troubleshoot during business hours.

    Enable auto-updates for these

    Turn on auto-updates for plugins that meet all three criteria: maintained by a well-known team, installed on millions of sites, and non-critical to front-end rendering.

    Examples: Yoast SEO, Akismet, Jetpack’s individual modules, Google Site Kit, and most analytics integrations. These teams test across thousands of hosting environments, and a breaking change would surface loudly within hours. They also tend to handle updates conservatively—bug fixes and security patches, not rewrites.

    If you’re running a newsletter archive or content site on WordPress, plugins like Redirection, Slim SEO, or public post embeds are usually safe bets. They’re stable, narrowly scoped, and updated infrequently.

    Leave these on manual

    Disable auto-updates for any plugin that controls caching, security rules, checkout flows, membership gates, or server-level behavior. That includes WP Rocket, Wordfence, WooCommerce, paid membership plugins, and anything that writes .htaccess rules.

    Also disable auto-updates for plugins you’ve customized—either via filters in your theme’s functions.php or by editing plugin files directly. An auto-update will overwrite your changes silently, and you’ll only notice when something stops working.

    If a plugin hasn’t been updated in over a year, don’t enable auto-updates. Dormant plugins that suddenly push an update are more likely to introduce compatibility issues, especially if WordPress core has moved forward two or three major versions in the meantime.

    The staging-first workflow

    If you’re managing a site that can’t afford any downtime—something generating revenue or serving a live audience—run a staging environment and test updates there first, even for low-risk plugins.

    Most managed WordPress hosts provide staging with one-click cloning. Push all available updates to staging, wait 24 hours, check front-end rendering and key workflows, then promote to production. This adds a day of latency, but it catches edge cases that wouldn’t surface in your local environment.

    For solo operators running lean, a middle path: enable auto-updates on 60–70% of installed plugins (the low-risk ones), and manually update the critical 30% once a week during a scheduled maintenance window. WordPress will email you when it auto-updates something, so you’ll have a log.

    When auto-updates fail silently

    WordPress’s auto-update routine doesn’t always tell you when something went wrong. If the update process times out—usually because a plugin’s install routine takes longer than PHP’s execution limit—the plugin is left in a half-updated state. The files are new, but the database schema didn’t migrate.

    This is rare but not impossible. It’s more common on shared hosting with aggressive resource limits. If you’re enabling auto-updates, check your hosting dashboard for PHP timeout settings. Anything below 60 seconds increases the chance of a partial update.

    One non-obvious safeguard: keep your total plugin count under 25. The more plugins WordPress has to evaluate during its twice-daily update check, the higher the cumulative risk that one update will collide with another or trigger a cascading failure.

    Want more operations breakdowns like this? Subscribe to One Two Three Send for weekly deep-dives on the tooling and workflows that run online businesses.

  • WordPress multisite network installs: when to split and when to merge

    WordPress multisite network installs: when to split and when to merge

    WordPress multisite network installs: when to split and when to merge
    Photo: Simone Bignetti via Wikimedia Commons (CC BY-SA 4.0)

    WordPress multisite lets you run dozens of sites from a single installation. Shared users, shared themes, one-click plugin updates across the network. It sounds like the dream infrastructure for anyone running multiple content projects.

    Until you try to install a plugin on site #4 that breaks checkout on site #9. Or your host tells you multisite counts as one site for billing—but throttles CPU after 50,000 monthly visits total. Or you want to sell one project and realise you can’t extract it without a database surgeon.

    Multisite makes sense in specific scenarios. Outside those scenarios, it creates more problems than it solves.

    When multisite works

    Multisite shines when you’re running a fleet of near-identical sites that share the same stack and never diverge.

    University department sites. Same theme, same plugins, same user roles. Central IT pushes updates; individual departments edit pages. No custom functionality per site.

    Franchise or multi-location businesses. Twenty gym locations, one brand, one CMS. Each location gets its own subdomain, but the plugin set, design, and functionality stay identical.

    Internal documentation or staging networks. You’re running test environments for client work and want to spin up isolated WordPress instances fast without separate hosting accounts.

    The common thread: low divergence. The moment one site needs WooCommerce, another needs a membership plugin, and a third runs a job board, multisite becomes a liability.

    Where it breaks

    Plugin conflicts cascade across the network. Activate a caching plugin on the network level, and it applies to every site—even the ones where it breaks the login flow or conflicts with another plugin active on a single sub-site.

    You can activate plugins per-site, but troubleshooting gets messy. Is the issue the network-activated SEO plugin, the site-specific form builder, or the theme? Multiply that across twelve sites and you’re spending more time debugging than publishing.

    Performance caps hit faster. Most managed WordPress hosts treat multisite as a single site for billing purposes—but apply the resource limits globally. If your plan includes 100,000 visits per month, that’s across all subsites combined. Three sites doing well means the rest get throttled.

    BigScoots and a handful of other hosts meter multisite by total resource usage rather than visit counts, but you’ll still hit I/O limits or MySQL connection caps faster than you expect when you’re sharing a database across ten active sites.

    You can’t sell or migrate one site cleanly. Each subsite lives in the same database with shared user tables and a shared uploads directory (unless you’ve rewritten the media handling). Extracting one site means exporting specific tables, rewriting URLs, and recreating users. It’s possible, but it’s never one-click.

    The split-or-merge decision tree

    Start with separate WordPress installs if:

    • Each project might need different plugins, especially anything that touches checkout, membership, or user roles
    • You plan to sell, transfer, or shut down individual sites
    • Traffic is uneven—one site gets 80% of visits and you want isolated performance
    • You’re working with different developers or agencies per project

    Go multisite if:

    • You’re running 5+ sites with identical functionality
    • Central control matters more than per-site flexibility
    • You’ll never migrate or sell individual sites
    • You have in-house WordPress expertise to manage network-level issues

    One middle path: use separate installs but centralize management with something like MainWP or ManageWP. You get one dashboard for updates and backups without the database coupling.

    Migrating out of multisite

    If you’re already on multisite and it’s not working, extraction is possible but manual.

    You’ll export the subsite’s tables (usually prefixed with wp_2_, wp_3_, etc.), rename them back to the standard wp_ prefix, search-replace the domain in the database, and move the /wp-content/uploads/sites/2/ folder into a new install’s /uploads/ directory.

    Budget four to six hours for the first one. After that, you’ll have a checklist and can move faster.

    Some hosts offer multisite-to-single-site migration as a service—expect to pay $150–$300 per site, and confirm they’ll handle user table merges and media-path rewrites.

    Want more infrastructure breakdowns like this? Subscribe to One Two Three Send and get one operator-focused deep-dive every day.

  • WordPress staging sync: what pushes live and what gets orphaned

    WordPress staging sync: what pushes live and what gets orphaned

    WordPress staging sync: what pushes live and what gets orphaned

    Most WordPress hosting control panels let you spin up a staging site with one click. You make changes, test them, then push to production. The promise is simple: clone, edit, deploy.

    The reality is messier. Staging sync isn’t a mirror—it’s selective. Files, database tables, and user-generated content follow different rules depending on your host and how the sync is configured. Push the wrong way and you’ll lose form submissions, overwrite live orders, or leave half your images behind.

    Here’s what actually happens when you sync from staging to live, and how to avoid the gaps.

    What staging sync transfers by default

    Most hosting providers—BigScoots, Kinsta, WP Engine, SiteGround—sync the same core elements when you push from staging to production:

    • Theme files and templates. Any changes to your active theme’s PHP, CSS, or JavaScript files move over.
    • Plugin files and updates. New plugins, deleted plugins, and updated plugin code all transfer.
    • WordPress core files. If you upgraded WordPress in staging, the live site gets the same version.
    • Database schema changes. New tables, altered columns, and modified indexes sync as part of the database push.
    • Posts, pages, and taxonomy changes. Any content you edited, published, or trashed in staging replaces the live version.

    That’s the happy path. The problems show up in what doesn’t sync automatically.

    What gets skipped or orphaned

    Staging environments are clones at the moment you create them. Once live users start interacting with your production site—submitting forms, uploading files, placing orders—those changes don’t exist in staging. When you push from staging to live, you’re overwriting the live database with a snapshot that’s hours or days old.

    User uploads. Files added to /wp-content/uploads/ on your live site after you cloned staging won’t exist in staging. When you push, most hosts sync the staging uploads folder to production, which means newer live uploads vanish unless you manually merge them. Some hosts offer a files-only sync option; use it if you’ve added media in both environments.

    Form submissions and user-generated content. If you’re running Gravity Forms, WPForms, or any contact plugin that stores entries in the database, those entries live in custom tables. Pushing staging to live replaces those tables with the staging versions—which don’t include submissions that arrived while you were editing. Same for comments, user registrations, and forum posts.

    WooCommerce or EDD orders. E-commerce data is the highest-risk category. Orders, customer accounts, and transaction logs written to the live database after staging was cloned will be overwritten. If you push staging to live without exporting and re-importing order data, you lose sales records. Most operators avoid this by never syncing the database for active stores, or by excluding specific tables in the sync settings.

    Plugin settings that reference URLs. Staging sites usually run on a subdomain like staging.yoursite.com. Plugins that store absolute URLs—like page builders, SEO tools, or caching plugins—may carry over staging URLs when you push. You’ll need to run a search-and-replace on the database post-sync to fix internal links.

    How to sync safely

    The safest workflow treats staging as a code and structure environment, not a content sandbox. Here’s the pattern that avoids data loss:

    1. Sync files and database separately. Most staging tools let you choose what to push: files only, database only, or both. If your live site has active users, push files first, then manually migrate only the database tables you’ve actually changed—typically wp_options for settings, and any custom tables your plugin added. Skip wp_posts, wp_comments, and wp_users unless you’re certain no one has added content on live.

    2. Exclude tables in your sync config. Kinsta and WP Engine both let you specify tables to exclude during a database push. Add your order tables, form entries, and user-generated content tables to the exclusion list. You can find the exact table names in phpMyAdmin or your hosting control panel’s database manager.

    3. Export and re-import critical data. If you must overwrite the live database, export the tables you care about first using WP-CLI, phpMyAdmin, or a plugin like WP Migrate DB. After the push, re-import them. It’s manual, but it’s the only way to preserve order history or form data when you’re replacing the entire database.

    4. Test on a disposable clone. Some hosts let you create multiple staging environments. If you’re making risky changes—switching page builders, restructuring taxonomy, or migrating to a new plugin—spin up a second staging site, push to it first, and confirm nothing breaks before touching production.

    What to check after every push

    Even a clean sync can leave edge cases. After pushing staging to live, check:

    • Search and replace: confirm staging.yoursite.com didn’t leak into post content, image URLs, or plugin settings.
    • Uploads folder: verify recent images and PDFs still exist. If they’re missing, copy them from a live backup or manually upload.
    • Form entries: if you run contact forms, check the entries table. If it’s empty post-sync, restore from backup.
    • Cache: flush object cache, page cache, and CDN cache. Stale cache can serve staging content or broken URLs for hours after a push.

    Staging sync is a code deployment tool, not a content management system. Treat it that way and you won’t lose data when you ship.

    Got a sync horror story or a host that handles this better? Hit reply—we’re tracking which platforms make this easier and which ones still make you export tables manually.

  • WordPress database indexes: what slows down queries at 100k rows

    WordPress database indexes: what slows down queries at 100k rows

    WordPress database indexes: what slows down queries at 100k rows
    Photo by Deng Xiang on Unsplash

    If your WordPress dashboard starts crawling around 100,000 posts or 500,000 comments, the bottleneck isn’t PHP or your hosting plan. It’s your database indexes.

    Indexes tell MySQL where to find data without scanning every row. When they’re missing or degraded, queries that should take 20 milliseconds start taking three seconds. Your admin becomes unusable. Plugin operations time out. Cron jobs pile up.

    Here’s what’s happening under the hood, when indexes stop helping, and how to diagnose the problem before it kills your site.

    What indexes actually do

    A database index works like a book’s table of contents. Without one, MySQL reads every row in a table to find what it needs—a full table scan. With an index, it jumps directly to the relevant rows.

    WordPress core ships with 43 indexes across 12 default tables. The most important ones sit on wp_posts and wp_postmeta:

    • type_status_date on wp_posts: speeds up queries that filter by post type, status, and date—basically every archive page and admin list
    • post_id on wp_postmeta: makes custom field lookups fast
    • meta_key on wp_postmeta: handles queries that search by meta key name

    These indexes matter most. If you’re running WooCommerce, the meta_key index on wp_postmeta gets hammered—product queries constantly filter by _price, _stock_status, and _sku.

    When indexes break down

    Indexes degrade in three situations:

    High cardinality on the wrong column. If a plugin adds a meta key that stores unique values for every post—say, a view count that increments on every page load—the index becomes less useful. MySQL still has to scan thousands of rows because the index can’t narrow results effectively.

    Composite indexes queried in the wrong order. The type_status_date index on wp_posts only works efficiently if your query filters by post type first, then status, then date. If a plugin queries by date alone, MySQL can’t use the index properly. It falls back to a slower scan.

    Table fragmentation after mass deletes. If you bulk-delete 50,000 spam comments or trashed posts, the table’s physical storage fragments. The index still points to the right rows, but MySQL has to skip over empty space. Performance drops even though the index itself is intact.

    How to diagnose index problems

    Most hosts don’t surface slow queries unless you ask. Here’s how to check:

    Enable MySQL slow query logging. Add this to your wp-config.php:

    define('SAVEQUERIES', true);

    Then install the Query Monitor plugin. It logs every query, execution time, and whether an index was used. Look for queries over 0.5 seconds that show “Using filesort” or “Using temporary” in the query plan—those are red flags.

    Check table size and row count. Run this query in phpMyAdmin or your host’s database tool:

    SELECT table_name, table_rows, data_length, index_length FROM information_schema.tables WHERE table_schema = 'your_database_name';

    If wp_postmeta has over 1 million rows and index_length is larger than data_length, your indexes are bloated. That’s normal for high-volume sites, but if queries are slow, it’s time to optimize.

    Look for missing custom indexes. Plugins that add custom post types or meta queries often don’t add their own indexes. If you’re running a directory site that filters by _city or _category meta keys, those fields need dedicated indexes. Without them, every search scans the entire wp_postmeta table.

    When to add a custom index

    You should add a custom index if:

    • A specific meta key appears in more than 25% of your site’s database queries
    • Query Monitor shows repeated slow queries filtering by the same field
    • You’re running a custom query that WordPress core doesn’t anticipate—like filtering products by a custom taxonomy and two meta fields simultaneously

    Adding an index is a single SQL command:

    CREATE INDEX idx_city ON wp_postmeta(meta_key, meta_value(20));

    That example indexes the first 20 characters of meta_value for any row where meta_key matches your filter. It won’t speed up every query, but for the specific use case it targets, it can cut execution time by 90%.

    One warning: indexes aren’t free. Every index slows down inserts and updates slightly, because MySQL has to maintain the index on every write. Don’t add indexes speculatively. Add them only after you’ve confirmed a specific query is slow and the index will help.

    If your WordPress site runs a content-heavy business—10,000+ posts, membership queries, or WooCommerce—indexes are the highest-leverage performance fix you can make. Most hosts won’t optimize them for you. You have to look.

    Want more infrastructure breakdowns like this? Subscribe to One Two Three Send—one operator-focused article every day, no fluff.

  • WordPress plugin staging environments: what gets cloned and what doesn’t

    WordPress plugin staging environments: what gets cloned and what doesn’t

    WordPress plugin staging environments: what gets cloned and what doesn't
    Photo: Nevin Aladağ via Wikimedia Commons (CC BY-SA 4.0)

    Most WordPress hosts now offer one-click staging environments. You spin up a copy of your live site, test a plugin update or theme change, then push it back. Clean, safe, professional.

    Except staging environments are selective clones. They copy most of your site, but not all of it—and the gaps aren’t always obvious until something breaks in production.

    If you run a content business on WordPress, you’ve probably assumed staging mirrors everything. It doesn’t. Here’s what actually transfers, what gets left behind, and how to avoid silent failures when you merge changes back to live.

    What staging environments actually copy

    A typical staging setup clones two things: your database and your file system. That includes:

    • WordPress core files
    • Plugins and themes
    • Uploads (images, PDFs, media library assets)
    • Posts, pages, and custom post types
    • User accounts, comments, and most settings

    Most hosts—BigScoots, Kinsta, WP Engine, Flywheel—use this model. The staging site gets its own subdomain (often staging.yourdomain.com or a randomized URL), and you work there without touching production.

    The clone happens at the database and filesystem level. Your staging environment is a snapshot, usually taken within the last few minutes. It’s current, but not live-synced.

    What doesn’t transfer—and why it matters

    Staging environments don’t replicate everything. Here’s what commonly gets left behind:

    External API connections. If your site talks to a payment processor, email platform, or CRM, those connections often stay pointed at production endpoints. Stripe webhooks, Mailchimp API keys, and analytics tracking codes don’t automatically switch to “test mode” just because you’re in staging. You can accidentally trigger real transactions, send live emails, or pollute your analytics with test traffic.

    Cron jobs and scheduled tasks. Staging clones your cron schedule, but it doesn’t always disable it. If you have a plugin that sends daily digest emails or publishes scheduled posts, those tasks might fire from staging—using production data. Some hosts disable wp-cron.php in staging by default. Others don’t.

    CDN and caching layers. Your staging site bypasses your CDN. That’s intentional—caching would defeat the purpose of testing—but it means performance in staging won’t match production. A plugin that works fine in staging might choke under real traffic once your CDN and object cache are back in the loop.

    Environment variables and secrets. If you store API keys or credentials in environment variables (via wp-config.php or server-level config), those may not carry over to staging. Some hosts isolate staging environments deliberately to prevent accidental key reuse. You’ll see “API key missing” errors that don’t appear in production.

    User-uploaded files after the snapshot. Staging clones your uploads directory at the moment you create it. Any images, PDFs, or media uploaded to production after that point won’t appear in staging. If you’re testing a gallery plugin or a membership site with gated downloads, you’re working with stale assets.

    What breaks when you push changes back

    Most staging tools let you push database changes, file changes, or both back to production. This is where silent failures happen.

    If you only push file changes (plugin updates, theme edits), you’re safe—unless those files expect database schema changes that didn’t carry over. A plugin update might add a new table or modify column types. If you skip the database push, production won’t match staging.

    If you push database changes, you’re overwriting production data. That includes posts published since you created staging, comments left by readers, new user signups, and form submissions. One-click database pushes are destructive. Most hosts warn you. Many operators click through anyway.

    The safest approach: push file changes automatically, migrate database changes manually using a migration plugin or SQL export. It’s slower, but you control what gets overwritten.

    How to test without poisoning production

    Before you work in staging, take three steps:

    Disable outbound email and API calls. Install a plugin like WP Mail Logging or use an SMTP plugin’s test mode. If your site integrates with Stripe, Mailchimp, or similar, swap in test API keys for staging. Don’t assume the platform knows you’re in a non-production environment.

    Check your cron schedule. Run wp cron event list via WP-CLI, or use a plugin like WP Crontrol to see what’s queued. Disable any tasks that send email, post to social media, or trigger external webhooks.

    Document what you change. Keep a text file listing every plugin you update, every setting you toggle, every theme file you edit. When you push back to production, you’ll know exactly what should change—and you can verify it did.

    Staging environments are useful, but they’re not magic mirrors. They’re partial clones with built-in gaps. The faster you map those gaps, the fewer surprises you’ll hit when you go live.

    Want more backend WordPress breakdowns? Subscribe to One Two Three Send—we cover the infrastructure stuff other newsletters skip.

  • WordPress database backups: what incremental snapshots skip

    WordPress database backups: what incremental snapshots skip

    WordPress database backups: what incremental snapshots skip
    Photo by CARTIST on Unsplash

    Incremental database backups sound efficient: only save what changed since the last snapshot, cut storage costs, speed up the process. Most WordPress backup plugins offer the option, and plenty of managed hosts enable it by default.

    But incremental backups make silent trade-offs. They skip tables that haven’t registered a timestamp change, ignore transient data that might matter during recovery, and often fail to capture schema modifications. If you’ve ever restored from an incremental backup and found missing comments, broken plugin settings, or mysteriously stale content, this is why.

    How incremental database backups decide what to save

    An incremental backup compares the current database state to the last full or incremental snapshot. It looks for changed rows, usually by checking a modified or updated_at timestamp column. If a row hasn’t been touched since the last backup, it gets skipped.

    That works well for posts, pages, and user accounts—tables with reliable timestamp tracking. It breaks down for:

    • Tables without timestamp columns. Some plugins store settings in custom tables with no date field. The backup tool can’t tell if the data changed, so it assumes it didn’t.
    • Schema changes. If you add a column, rename a table, or adjust an index, the incremental job may not flag that as a data change. The new structure exists, but the backup doesn’t record it.
    • Transient and cache tables. WordPress uses wp_options to store transients—temporary data with expiration timestamps. Incremental backups often exclude transients by design, assuming they’re disposable. That’s true for caching, but some plugins misuse transients to store semi-permanent config.
    • Comment metadata. If a comment gets edited or a new meta field is added via a plugin, the parent comment row’s timestamp might not update. The incremental scan misses it.

    You won’t notice the gaps until you restore. Then you’re troubleshooting why a contact form stopped working or why a membership plugin forgot half its settings.

    When incremental backups make sense

    If your site publishes daily content and you’re backing up multiple times per day, incremental snapshots cut down on redundant transfers. A site publishing ten posts a day doesn’t need to re-export its entire wp_posts table every hour—just the new rows.

    Incremental backups also help when bandwidth or storage is metered. A full WordPress database backup on a site with 50,000 posts and 200,000 comments can run 500 MB to 1 GB compressed. Doing that hourly gets expensive. An incremental job might be 5–20 MB.

    But incremental-only strategies fail during migrations, plugin conflicts, or schema drift. If your last full backup was three months ago and you’ve been running incrementals since, you’re stitching together dozens of delta files during a restore. One corrupted increment in the chain, and the whole recovery breaks.

    How to layer full and incremental backups without waste

    Run a full database backup weekly. Run incrementals daily or after major content pushes. Store both.

    Most backup plugins let you set independent schedules. UpdraftPlus, for example, supports separate intervals for full and incremental jobs. BackupBuddy and BlogVault do the same. If you’re on managed WordPress hosting with BigScoots or a similar provider, check whether their automated backups are full or incremental—and whether you can trigger a manual full snapshot before big updates.

    Tag your full backups clearly. If you’re using a plugin that stores backups in Amazon S3, Google Cloud Storage, or Backblaze B2, add a naming convention: full-2026-09-27.sql.gz vs. incr-2026-09-27-14-00.sql.gz. When you need to restore, you’ll know which file is the anchor.

    Test a restore at least once per quarter. Spin up a local WordPress instance with Local by Flywheel or a staging environment, restore from your most recent full backup, then layer on the incrementals. Confirm that everything loads: plugins activate, custom post types appear, user roles are intact, and forms submit. If something’s missing, adjust your backup scope or switch to full-only until you know why.

    What to check in your plugin’s incremental settings

    Open your backup plugin’s settings and look for these options:

    • Table exclusion list. Some plugins exclude wp_options transients by default. If you rely on plugins that store settings there, remove the exclusion or switch to full backups.
    • Change detection method. Does the plugin use timestamp columns, row checksums, or binary logs? Checksum-based detection catches more changes but takes longer. Timestamp detection is faster but misses schema edits.
    • Retention policy. How many incremental snapshots does the plugin keep? If it’s set to retain only the last seven, and your last full backup was two weeks ago, you can’t reconstruct the database past seven days.
    • Manual trigger. Can you force a full backup on demand? You should be able to run one before plugin updates, theme changes, or bulk imports—events that modify the database in ways incrementals might miss.

    If your plugin doesn’t expose these controls, consider switching. UpdraftPlus, BackWPup, and Duplicator all offer granular settings. Managed hosts often abstract this away—ask support whether their incremental backups include schema changes and plugin config tables.

    Want more breakdowns of the infrastructure tools that underpin online businesses? Subscribe to One Two Three Send and get one operator-focused deep dive every day.