Category: Hosting

  • Hosting control panels: cPanel, Plesk, and custom—what you gain and lose

    Hosting control panels: cPanel, Plesk, and custom—what you gain and lose

    Hosting control panels: cPanel, Plesk, and custom—what you gain and lose
    Photo by Kevin Ache on Unsplash

    Most operators never choose their hosting control panel. It comes bundled with the plan. But the interface you use to manage domains, databases, SSL certificates, and email accounts shapes how quickly you can fix problems, migrate sites, and scale your infrastructure.

    Here’s an honest look at the three control panel architectures you’ll encounter: cPanel, Plesk, and custom dashboards built by hosts. Each has clear trade-offs for solo operators and small teams running content businesses.

    cPanel: ubiquitous, consistent, but aging

    cPanel dominates shared and reseller hosting. If you’ve ever logged into a budget host, you’ve probably seen its File Manager, phpMyAdmin shortcut, and domain management grid.

    Pros:

    • Nearly universal support—documentation, tutorials, and third-party tools assume cPanel by default
    • Direct database access via phpMyAdmin is fast; no ticket required to create or drop tables
    • Email account setup is self-service, including forwarders and autoresponders
    • Backup restoration is straightforward: upload a .tar.gz archive and cPanel extracts files, databases, and email in one pass

    Cons:

    • Licensing costs increased sharply in 2019; many budget hosts passed fees to customers or migrated away
    • Interface design hasn’t changed much since 2015—it’s functional but slow to navigate on mobile
    • SSL automation lags behind newer panels; Let’s Encrypt works, but renewal sometimes requires manual nudging
    • Resource usage graphs update every five minutes, not in real time—problematic when diagnosing a traffic spike

    Best for: Operators who need predictable workflows across multiple hosts, or who run reseller setups where clients expect a familiar interface.

    Pricing note: Hosts typically bundle cPanel into plans. Standalone licenses start around $15/month for VPS users, but you’ll rarely pay that directly unless self-managing a server.

    Plesk: Windows-friendly, modular, underappreciated

    Plesk is cPanel’s main competitor, but it’s more common on European hosts and VPS providers. It runs on both Linux and Windows Server—one reason it’s popular for operators juggling WordPress and ASP.NET projects.

    Pros:

    • Cleaner UI than cPanel; tabbed navigation and search make it faster to find settings
    • WordPress Toolkit is built-in: one-click staging, cloning, security hardening, and plugin updates from the Plesk dashboard
    • Git integration is native—deploy from a repository without SSH or third-party plugins
    • Docker container management is available in most plans; useful if you’re experimenting with headless CMSes or Node.js apps

    Cons:

    • Smaller documentation ecosystem—you’ll find fewer DigitalOcean-style tutorials and Stack Overflow threads
    • Extension marketplace is useful but inconsistent; some plugins cost extra and update irregularly
    • Email management isn’t as granular as cPanel; advanced filtering requires diving into config files
    • Backup scheduling is less flexible—you can’t easily script partial backups of specific directories

    Best for: Operators who value a modern interface and need WordPress-specific tooling baked into the host dashboard. Also a good fit if you’re running mixed environments (WordPress plus a Node API, for example).

    Pricing note: Plesk licensing is comparable to cPanel—around $10–$20/month for VPS plans, bundled into managed hosting.

    Custom dashboards: fast, opinionated, locked-in

    Premium managed WordPress hosts—Kinsta, WP Engine, Flywheel, and others—skip cPanel and Plesk entirely. They build proprietary control panels optimized for WordPress workflows.

    Pros:

    • Staging environments deploy in seconds; you don’t manually clone databases or rewrite URLs
    • SSL, CDN, and DNS configuration happen in a few clicks—no certificate chains or nameserver confusion
    • Real-time performance graphs and error logs surface issues faster than cPanel’s delayed metrics
    • Automatic plugin and core updates can be scheduled per-site, with rollback options

    Cons:

    • No direct database access in most cases—you export a SQL dump or use phpMyAdmin via a secondary link, slowing down manual fixes
    • Email hosting is often excluded; you’ll need to integrate Google Workspace, Fastmail, or a transactional service like Postmark
    • Migration away from the host is harder—no standardized backup format means exporting files, databases, and DNS separately
    • Limited flexibility for non-WordPress projects; if you need a static site or a Rails app, you’re out of luck

    Best for: Operators focused exclusively on WordPress who value speed and simplicity over control. If you never touch wp-config.php or run custom cron jobs, a custom dashboard is faster.

    Pricing note: Custom dashboards come with premium managed plans—typically $30–$100+/month per site, depending on traffic and features.

    Which one matters for your business?

    If you’re migrating between hosts frequently, learning multiple clients’ sites, or troubleshooting unfamiliar setups, cPanel’s ubiquity wins. The learning curve is shallow, and you’ll find answers fast.

    If you’re managing a portfolio of WordPress sites and want tooling that reduces manual work—staging, Git deploys, security scans—Plesk or a custom dashboard cuts hours per month.

    And if you’re running a single high-traffic WordPress site where performance and uptime matter more than flexibility, a custom dashboard from a managed host like BigScoots eliminates friction.

    The wrong choice doesn’t break your business. But the right one makes every routine task—SSL renewals, database exports, email setup—faster. And for solo operators, that’s the difference between spending an hour on infrastructure and spending that hour writing.

    What control panel are you using, and what’s the one feature you wish it had? Reply to this email—I’m cataloging the gaps between what hosts offer and what operators actually need.

  • WordPress plugin conflict logs: where they live and what to search for

    WordPress plugin conflict logs: where they live and what to search for

    WordPress plugin conflict logs: where they live and what to search for

    Most WordPress plugin conflicts don’t throw a visible error. The site looks fine. The dashboard loads. But form submissions stop working, scheduled posts don’t publish, or your automation plugin silently skips every third webhook.

    The conflict is logged—WordPress writes it somewhere—but most operators don’t know where to look or what the log entries actually mean.

    Here’s how to find conflict logs, read them, and figure out which plugin is causing the problem without disabling everything one by one.

    Where WordPress writes plugin conflict data

    WordPress doesn’t have a single “conflict log.” It writes errors to three places depending on your hosting setup:

    • debug.log — lives in /wp-content/ if WP_DEBUG_LOG is enabled in wp-config.php
    • PHP error log — location varies by host; often /var/log/ or accessible via cPanel
    • Server error log — Apache or Nginx writes fatal errors here; usually needs SSH or hosting dashboard access

    If you’re on managed WordPress hosting like BigScoots, Kinsta, or Flywheel, the dashboard usually surfaces recent errors without file access. Look for “Error Logs” or “Site Health” in the admin panel.

    To enable debug logging manually, add this to your wp-config.php file just above the line that says “That’s all, stop editing”:

    define('WP_DEBUG', true);
    define('WP_DEBUG_LOG', true);
    define('WP_DEBUG_DISPLAY', false);

    This writes errors to /wp-content/debug.log without showing them to visitors. Leave it on for 24 hours, then check the file.

    What plugin conflict entries look like

    A real conflict log entry looks like this:

    [21-Sep-2026 14:32:18 UTC] PHP Fatal error: Cannot redeclare class WP_REST_Controller in /wp-content/plugins/plugin-a/includes/rest-api.php on line 12

    The key patterns to search for:

    • “Cannot redeclare” — two plugins define the same function or class
    • “Call to undefined function” — one plugin expects another to load first and it didn’t
    • “Maximum execution time exceeded” — infinite loop between two plugins
    • “Headers already sent” — one plugin outputs content before another tries to set cookies or redirects

    The file path tells you which plugin triggered the error. If you see /wp-content/plugins/plugin-a/ and /wp-content/plugins/plugin-b/ in consecutive lines with the same timestamp, that’s your conflict pair.

    Reading logs without file access

    If your host doesn’t offer file access and you don’t have SSH, install the free “WP Log Viewer” or “Error Log Monitor” plugin. Both surface debug.log contents in the WordPress admin.

    Once installed, go to Tools → Error Log (the menu label varies). You’ll see the most recent entries at the top. Use your browser’s find function (Ctrl+F or Cmd+F) to search for the patterns above.

    Most conflicts happen during these events:

    • Plugin activation or deactivation
    • WordPress or PHP version updates
    • Cron jobs running in the background
    • Form submissions or checkout processes

    Filter the log by timestamp to isolate when the problem started. If a user reported “checkout stopped working on Tuesday,” look at entries from Tuesday morning onward.

    One non-obvious detail: load order matters

    WordPress loads plugins alphabetically by folder name. If Plugin A expects Plugin B to register a custom post type first, but “plugin-a” loads before “plugin-b,” the conflict won’t show up until a specific feature is triggered.

    The log will say Call to undefined function register_cpt_from_plugin_b() even though both plugins are active and working independently.

    The fix: some plugins offer a “load priority” setting in their options. If not, you can rename the plugin folder (via FTP or file manager) to change load order—prefix the one that needs to load first with 0- or aaa-. This is hacky but works when the plugin developer won’t fix it.

    Before you do that, check if one of the plugins has a dependency declaration in its header. Open the main plugin file and look for Requires Plugins: in the comment block. If it’s there, WordPress 6.5+ enforces load order automatically. If it’s missing, the developer didn’t specify dependencies—which is why the conflict exists.

    When to stop reading logs and just test

    If the log shows 200+ lines of the same error repeating, don’t parse every entry. The conflict is clear. Disable the plugin named in the file path, clear the log, and see if the error stops.

    If two plugins both appear in the log but you can’t tell which one is at fault, disable the one updated most recently. Plugin updates often introduce conflicts with older code that hasn’t been patched in years.

    Keep a staging site or local copy running if you manage multiple WordPress installs. Test plugin updates there first, enable debug logging, and scan for conflicts before pushing to production. It’s faster than debugging live.

    Hit reply if you’ve found a plugin conflict pattern that logs don’t surface. Some conflicts only show up in browser console errors or network request failures—we’ll cover those in a future piece if there’s enough interest.

  • WordPress database table prefixes: when default wp_ becomes a target

    WordPress database table prefixes: when default wp_ becomes a target

    WordPress database table prefixes: when default wp_ becomes a target
    Photo by Stephen Phillips – Hostreviews.co.uk on Unsplash

    Every WordPress install stores its data in database tables. By default, those tables start with wp_—so you get wp_posts, wp_users, wp_options, and so on. That prefix is editable during installation, and some security guides suggest changing it to something unique as a way to obscure table names from attackers.

    The question: does it actually matter?

    What the prefix does

    The table prefix exists to let you run multiple WordPress sites in a single database. If you install two sites and give them different prefixes—say wp_ and blog_—they won’t collide. Each gets its own set of tables in the same MySQL or MariaDB instance.

    From a security perspective, the prefix doesn’t encrypt anything or enforce access control. It’s just a namespace. If an attacker already has database access—via SQL injection, compromised credentials, or a server breach—they can list all tables with a single SHOW TABLES query. The prefix won’t hide anything.

    So why do security checklists still recommend changing it?

    Where obscurity helps (a little)

    Changing the prefix makes automated attacks slightly less efficient. Bots that scan for vulnerable plugins often assume default table names when crafting exploit payloads. If your prefix is j8k_ instead of wp_, a hardcoded query might fail—and the bot moves on.

    It’s security through obscurity, which isn’t a substitute for patching, but it’s also not useless. Think of it as one thin layer in a stack that should also include:

    • Strong database user passwords
    • Restricted database host access (localhost-only when possible)
    • Regular plugin and core updates
    • File permission hardening

    None of those are optional. The prefix change is optional—but low-cost.

    When to change it, and when it’s too late

    If you’re installing a fresh site, changing the prefix takes five seconds. Most hosts let you set it during the WordPress auto-installer, or you can edit wp-config.php before running the famous five-minute install. BigScoots, for example, randomizes the prefix by default in their managed WordPress environments.

    If your site is already live, changing the prefix is riskier. You need to:

    • Rename every table in the database (via phpMyAdmin or a plugin like Brozzme DB Prefix)
    • Update the $table_prefix variable in wp-config.php
    • Run queries to update the usermeta and options rows that still reference the old prefix

    Miss one reference and parts of your site break—widget settings vanish, user roles reset, plugin data disappears. It’s doable, but it’s not a casual edit. Most operators who’ve been running for months or years don’t bother.

    What security researchers actually say

    OWASP and WordPress’s own hardening documentation don’t list prefix changes as a high-priority step. They’re focused on:

    • Limiting database user privileges (no DROP or CREATE USER rights)
    • Keeping WordPress and plugins updated
    • Using prepared statements in custom code to prevent SQL injection

    The prefix is mentioned as a “defense in depth” tactic—useful if you’re already doing the important stuff, and harmless if automated during setup.

    The one scenario where it matters more: shared hosting environments where multiple sites share the same database (different prefixes, same DB). If one site is compromised, a non-standard prefix makes lateral movement slightly harder. But if you’re on shared hosting, the bigger risk is usually filesystem access, not database enumeration.

    The non-obvious detail: plugin compatibility

    Most plugins query tables using WordPress’s $wpdb global, which automatically applies the correct prefix. But older or poorly coded plugins sometimes hardcode wp_ in raw SQL. If you change your prefix to something custom, those queries fail silently—or worse, throw errors that expose your database structure in server logs.

    Before you change a live site’s prefix, audit your plugin list. Anything that hasn’t been updated in two years is a red flag. Test on a staging environment first.

    Practical takeaway

    If you’re spinning up a new site, change the prefix during install. It costs nothing and makes you a marginally harder target for lazy bots. If your site is already live and you haven’t had a breach, don’t bother—spend that time updating plugins, tightening file permissions, and enabling two-factor auth instead.

    Security is a stack, not a switch. The prefix is one tile in a much larger mosaic.

    Want more WordPress infrastructure breakdowns? Subscribe to One Two Three Send and get one operator-focused deep-dive every day—no fluff, no affiliate spam, just the mechanics that matter.

  • WordPress object cache: what it stores and when to purge it

    WordPress object cache: what it stores and when to purge it

    WordPress object cache: what it stores and when to purge it
    Photo: Matinbeigi via Wikimedia Commons (CC BY-SA 4.0)

    Most WordPress performance guides tell you to enable object caching. Few explain what it actually caches—or when clearing it causes more problems than it solves.

    Object caching sits between WordPress and your database. When a plugin or theme requests data—post metadata, user details, term relationships—WordPress checks the cache first. If the data exists in memory, the database query never runs. If not, WordPress queries the database, stores the result, and serves it from cache on the next request.

    This matters more as your site grows. A single page load on a membership site might trigger 200+ database queries. Object caching can cut that to under 50.

    What object cache actually stores

    Object cache doesn’t store rendered HTML. It stores discrete pieces of data WordPress requests repeatedly:

    • Post metadata: custom fields, featured image IDs, post status
    • Taxonomy terms: categories, tags, and custom taxonomies assigned to posts
    • User data: roles, capabilities, profile fields
    • Options table entries: site settings, plugin configurations
    • Transients: time-limited data stored by plugins (API responses, remote file checks)

    Page caching—what most CDNs and caching plugins do—stores the final HTML output. Object caching operates one layer deeper, at the data-retrieval level. That’s why you often run both: page cache for anonymous visitors, object cache for logged-in users and admin requests.

    Redis and Memcached are the two dominant backends. Redis persists data to disk and survives server restarts; Memcached lives entirely in RAM and flushes on reboot. For solo operators, Redis is the safer default. Most managed WordPress hosts offer it as a toggle in the dashboard.

    When purging object cache breaks workflows

    Flushing object cache is a common troubleshooting step. You update a plugin, something looks wrong, you clear all caches. That works—until it doesn’t.

    Some plugins store non-regenerable data in transients. If you flush the cache mid-process, the plugin loses track of where it was. I’ve seen this break:

    • Bulk import tools that cache progress state between batches
    • OAuth tokens stored as transients (rare, but it happens)
    • Membership plugins tracking trial eligibility windows

    If a process starts behaving erratically after a cache flush, check whether the plugin documentation warns against it. WP-CLI’s wp cache flush is instant and irreversible—there’s no undo.

    A safer alternative: flush selectively. Most object cache plugins let you clear specific cache groups (users, posts, terms) instead of nuking everything. WP Rocket and LiteSpeed Cache both expose group-level controls in their dashboards.

    When object cache doesn’t help

    Object caching speeds up repeated queries. If your site serves mostly anonymous traffic and you’re already using page caching, object cache adds minimal benefit—the page cache serves HTML before WordPress even boots.

    It shines in three scenarios:

    • Membership or user-specific content: logged-in users bypass page cache, but object cache still cuts database load
    • High-traffic admin areas: dashboard requests hit the database hard; object cache reduces query time
    • WooCommerce or other plugin-heavy builds: plugins query metadata constantly; caching eliminates redundant lookups

    If you’re running a simple blog with static pages and no user accounts, object cache won’t make a perceptible difference. Your hosting plan’s resources matter more.

    Configuration detail most hosts skip

    Default object cache configurations use the same Redis or Memcached instance for every site on a multisite network. That means a cache flush on Site A also purges Site B’s data.

    If you’re running multiple sites—even just a staging and production environment—set unique cache key prefixes. In Redis, that’s the WP_CACHE_KEY_SALT constant in wp-config.php:

    define('WP_CACHE_KEY_SALT', 'mysite_prod');

    This ensures staging flushes don’t touch production cache, and vice versa. Managed hosts sometimes set this automatically. If you’re on a VPS or managing your own stack, you configure it manually.

    One more detail: object cache doesn’t replace database optimisation. If you’re running slow queries, caching only hides the symptom. Use Query Monitor to log what’s actually hitting the database, then optimise the queries or add indexes. Cache speeds up reads; it can’t fix inefficient writes or missing foreign keys.

    Have a caching setup that doesn’t fit the standard advice? Reply and tell us what you’re running—we read every response.

  • WordPress plugin auto-updates: what breaks when background jobs fail

    WordPress plugin auto-updates are supposed to be set-and-forget. You toggle the setting, and your site stays patched without you logging into the dashboard every Tuesday. Except when the update runs halfway, stalls, and leaves your site in a state that doesn’t throw an error but quietly breaks functionality you won’t notice until a reader emails you three days later.

    This isn’t a rare edge case. It happens because WordPress doesn’t use server cron—it uses WP-Cron, a pseudo-cron system that fires when someone visits your site. If your traffic is low, if your caching is aggressive, or if your hosting provider throttles background requests, WP-Cron jobs can skip, delay, or terminate mid-execution. Auto-updates are one of those jobs.

    How WordPress plugin auto-updates actually run

    When you enable auto-updates for a plugin, WordPress schedules a background task via WP-Cron. Twice daily, it checks for new versions. If an update is available, it triggers a multi-step process: download the new plugin zip, deactivate the old version, extract the new files, reactivate, and run any database migrations the plugin author included.

    Each step depends on the previous one completing. If your server times out, if PHP hits its memory limit, or if WP-Cron doesn’t fire because no one visited your site in the last twelve hours, the process halts. WordPress doesn’t retry. It doesn’t log the failure in your admin dashboard. The plugin shows as the new version number, but the files might be a mix of old and new, or the database schema might still be two versions behind.

    You’ll notice this when a form stops submitting, when an API integration returns a 500 error, or when your members area throws a white screen. The error logs—if your host surfaces them—will show a missing function or a table that doesn’t exist. The plugin version in your dashboard will say 2.8.4, but the actual code running will be 2.8.2 with one updated file.

    What causes background job failures

    Three common scenarios stall WP-Cron-based auto-updates. First: aggressive full-page caching. If every request is served from cache, WP-Cron never fires. Plugins like WP Rocket and hosts like BigScoots often bypass cache for logged-in users, but if you’re not logging in regularly and your traffic is mostly anonymous readers hitting cached pages, your cron jobs can go days without running.

    Second: low memory limits. Shared hosting accounts often cap PHP memory at 128MB or 256MB. Plugin updates—especially for page builders or membership plugins—can exceed that during extraction and activation. The process dies silently, and WordPress moves on.

    Third: server-level request timeouts. If your host enforces a 30-second execution limit and your plugin takes 35 seconds to update, the job terminates before completion. No retry, no notification.

    How to fix this before it breaks your site

    Disable WP-Cron and set up real server cron. Most hosts let you add a cron job in cPanel or via SSH. Add this to your wp-config.php file, above the “stop editing” line:

    define('DISABLE_WP_CRON', true);

    Then create a server cron job that runs every fifteen minutes:
    */15 * * * * wget -q -O - https://yourdomain.com/wp-cron.php?doing_wp_cron >/dev/null 2>&1

    Replace wget with curl if your server doesn’t have wget installed. This forces WP-Cron to fire on schedule, regardless of traffic.

    Second: increase your PHP memory limit. Add this to wp-config.php:
    define('WP_MEMORY_LIMIT', '512M');

    If your host restricts this, ask support to raise it or switch to a host that gives you control. Most VPS and managed WordPress hosts let you set this yourself.

    Third: monitor plugin update logs. Install a plugin like WP Crontrol to see which cron jobs are scheduled, when they last ran, and whether they completed. If you see wp_update_plugins or wp_maybe_auto_update stuck in the queue for days, your auto-updates aren’t running.

    When to disable auto-updates entirely

    If your site is mission-critical—handling payments, managing memberships, running a course platform—auto-updates introduce risk you don’t need. A plugin author can push a breaking change, and you won’t know until your checkout stops working. Manual updates with a staging site catch this. Auto-updates don’t.

    For solo operators running content sites, auto-updates are convenient if your cron setup is solid. For teams running revenue-dependent infrastructure, the trade-off isn’t worth it. Test updates in staging, deploy during low-traffic hours, and keep auto-updates off for plugins that touch payments, user authentication, or data migrations.

    If you do keep auto-updates enabled, audit your WP-Cron health quarterly. Check that jobs are firing, that memory limits are adequate, and that no plugin updates are stuck half-installed. That ten-minute audit prevents the three-hour debugging session when something silently breaks.

    Want more infrastructure breakdowns? Reply with the hosting or plugin setup that’s giving you trouble—we’ll cover it in a future piece.

  • WordPress multisite network admin: when one dashboard controls fifty sites

    WordPress multisite network admin: when one dashboard controls fifty sites

    WordPress multisite network admin: when one dashboard controls fifty sites
    Photo by Team Nocoloco on Unsplash

    WordPress multisite lets you run dozens—or hundreds—of sites from a single installation. One database, one set of core files, one admin login that controls everything. It’s how WordPress.com hosts millions of blogs, and it’s available in every self-hosted WordPress install.

    But the network admin dashboard isn’t just a beefed-up version of the regular WordPress admin. It works differently, exposes different controls, and introduces failure modes you won’t encounter on a single site. If you’re running multiple content sites, client projects, or testing environments, understanding what network admin actually does will save you hours of troubleshooting.

    What network admin controls (and what it doesn’t)

    When you enable multisite, WordPress splits its admin interface into two layers. Each site still has its own dashboard where editors manage posts, pages, and basic settings. The network admin sits above that, accessible only to super admins, and controls infrastructure shared across all sites.

    From the network admin dashboard, you can:

    • Add, delete, archive, or mark sites as spam
    • Install plugins and themes once, then enable them selectively per site
    • Create new users with network-wide access or restrict them to specific sites
    • Configure domain mapping, so different sites answer to different domains
    • Set upload limits, storage quotas, and permitted file types globally

    You cannot directly edit another site’s content from network admin. To publish a post on Site B, you still need to switch to Site B’s dashboard. Network admin is about infrastructure and permissions, not day-to-day content operations.

    Plugin and theme activation works in two steps

    On a single WordPress site, you install a plugin and activate it. Done. On multisite, installation and activation are separate, and both require network admin access.

    First, you network-install the plugin—uploading it or pulling it from the repository. At this stage, it’s dormant. No site can use it yet. Then you either network-activate it (turning it on for every site instantly) or enable it per-site, letting individual site admins activate it themselves.

    This two-step design prevents rogue site admins from installing code network-wide. It also means you can test a plugin on one site before rolling it out. But it introduces a common mistake: activating a plugin on one site, seeing it work, then wondering why it’s missing on Site #7. You have to check both the network plugins page and each site’s plugins page to know what’s actually running.

    Themes work the same way. Network-enable a theme, and it appears in every site’s theme picker. If you don’t enable it, site admins won’t see it at all—even though the files are physically installed.

    When multisite makes sense (and when it’s overkill)

    Multisite shines when you’re running multiple sites that share the same plugin stack, user base, or design system. Examples:

    • A media company publishing five topical sites under different domains
    • An agency managing client sites from one hosting account
    • A solo operator running separate content brands that share backend infrastructure
    • A staging/production split where both environments live in one installation

    Multisite is not a good fit if your sites need fundamentally different plugins, have separate user bases with no shared logins, or require independent backups and update schedules. In those cases, separate WordPress installs—each with its own database and admin—are simpler to manage and easier to migrate or sell later.

    Multisite also complicates hosting. Not every managed WordPress host supports it. Those that do often charge more, because resource limits (disk, CPU, database queries) now apply to the entire network rather than one site. If Site #3 gets traffic-spiked and maxes out your database connection pool, every site in the network slows down.

    The non-obvious tip: use subdirectory mode unless you control DNS

    When you set up multisite, WordPress asks whether new sites should use subdomains (site2.example.com) or subdirectories (example.com/site2). Both work, but subdirectories are far easier to manage unless you already have wildcard DNS and SSL configured.

    Subdomain mode requires a wildcard DNS A record pointing *.example.com to your server, plus a wildcard SSL certificate (or a host that auto-provisions Let’s Encrypt certs per subdomain). If you don’t control DNS—say, you’re on a shared host or using a domain registrar with limited DNS editors—subdomain mode breaks. New sites won’t resolve, and you’ll chase certificate errors for hours.

    Subdirectory mode just works. No DNS changes, no SSL gymnastics. The tradeoff: URLs look less independent. But if you’re running a network for operational efficiency rather than separate brand identity, subdirectories save you a week of troubleshooting.

    WordPress multisite isn’t a feature most operators need. But if you’re managing more than three sites with overlapping workflows, it’s worth the learning curve—just expect to spend a day reading documentation before your first network goes live.

    Got a question about WordPress infrastructure or any other online-business tool? Reply to this email—I answer every one, and reader questions often become future articles.

  • WordPress CDN purge delays: what ‘instant’ invalidation really means

    WordPress CDN purge delays: what ‘instant’ invalidation really means

    WordPress CDN purge delays: what 'instant' invalidation really means
    Photo by Erik Mclean on Unsplash

    You push a WordPress post update, hit the CDN purge button, and refresh the page. The old version still loads. You check the dashboard—it says the cache cleared thirty seconds ago. So why is your reader in Singapore still seeing yesterday’s headline?

    CDN cache invalidation isn’t instant, even when providers claim sub-second purge times. What they measure and what you experience are two different things.

    What “purge time” actually measures

    When Cloudflare or BunnyCDN reports a three-second purge, they’re measuring how long it takes their control plane to mark the cached object as stale across their network. That’s an internal API call, not the moment your updated content becomes visible globally.

    Propagation delay comes from three sources:

    • Edge node sync latency: The purge command reaches regional POPs (points of presence) at different times. A node in Frankfurt might invalidate in two seconds; one in Sydney might take twelve.
    • In-flight requests: If a user’s browser already fetched the HTML but is still loading CSS and images, those assets may serve from cache even after purge completes.
    • Browser cache headers: If your origin sent Cache-Control: max-age=3600, the browser won’t even ask the CDN for an hour, regardless of purge status.

    Most CDN dashboards report control-plane purge time, not edge-node propagation time. The difference can be thirty seconds to two minutes under normal conditions.

    How WordPress caching plugins trigger purges

    Plugins like WP Rocket, W3 Total Cache, and LiteSpeed Cache hook into WordPress’s clean_post_cache action. When you update a post, they fire a purge request to your CDN’s API.

    But the plugin only knows the purge request sent—it doesn’t wait for confirmation that all edge nodes updated. If your CDN API returns a 200 status after queuing the purge, the plugin considers it done.

    This creates a false positive: the plugin shows a success message, but the purge is still propagating. If you immediately test the live URL, you might hit an edge node that hasn’t received the invalidation yet.

    Some CDNs—Cloudflare included—offer a “purge everything” option that’s faster than purging individual URLs. It’s a sledgehammer, but for time-sensitive updates (like fixing a pricing error in a sales post), it’s often the safer choice.

    Testing purge propagation across regions

    Don’t trust the dashboard timestamp. Test actual propagation using a multi-region tool or curl from different locations.

    Here’s a quick manual test using curl and a timestamp query string:

    curl -I "https://yoursite.com/post-slug/?t=$(date +%s)"

    Check the CF-Cache-Status (Cloudflare), X-Cache (BunnyCDN), or equivalent header. If it says HIT, that edge node is still serving cached content. Run this from multiple geographic locations—services like Pingdom or GTmetrix can help—to see how long full propagation actually takes.

    In practice, expect sixty to ninety seconds for global propagation on most CDNs, even when the dashboard says “purged” after five seconds.

    When purge delays break workflows

    Two scenarios make this particularly painful:

    Newsletter send timing: You schedule a post to publish at 9:00 AM, then queue a newsletter linking to it at 9:05 AM. If the CDN hasn’t fully propagated, early openers see a 404 or stale content. Add a ten-minute buffer, or use a staging URL in the newsletter and redirect after confirming the purge.

    Sponsored content updates: A sponsor asks you to correct a product name or price. You make the edit, purge, and send them the updated link. They check it thirty seconds later and still see the error. Now you’re explaining CDN propagation on a support call.

    For critical updates, use a purge-and-verify workflow: trigger the purge, wait two minutes, then test the live URL from at least two geographic regions before confirming the change is live.

    Want more infrastructure deep-dives like this? Subscribe to One Two Three Send for weekly breakdowns of the tools and systems that actually run online businesses—no fluff, just what works and what breaks.

    Reducing purge delays

    You can’t eliminate propagation time, but you can reduce uncertainty:

    • Set shorter max-age values on high-churn content (e.g., homepage, latest posts). Sixty seconds instead of 3600 means users re-check the CDN more frequently.
    • Use versioned asset URLs (e.g., style.css?v=1.2.3) instead of purging CSS and JS. WordPress plugins like WP Rocket do this automatically.
    • Enable stale-while-revalidate headers (Cache-Control: max-age=60, stale-while-revalidate=300) so the CDN serves stale content while fetching a fresh copy in the background.

    And if you’re on a budget host with inconsistent purge APIs, consider switching to a provider with better WordPress integration. BigScoots, for example, includes enterprise CDN tooling even on shared plans, with purge confirmation hooks that actually report edge-node status.

    The next time your CDN dashboard says “purged,” give it two minutes and a multi-region test before you trust it. Instant invalidation is a marketing term, not a technical reality.

  • WordPress plugin update rollbacks: how automatic reversions work

    WordPress plugin update rollbacks: how automatic reversions work

    WordPress plugin update rollbacks: how automatic reversions work
    Photo: Simpson, Thomas via Wikimedia Commons (Public domain)

    WordPress 6.3 shipped a feature most operators don’t know exists until it saves them: automatic plugin rollback after a fatal error. If a plugin update crashes your site, WordPress will—under specific conditions—revert to the previous version without you touching anything.

    That safety net isn’t as wide as it sounds. Understanding when it fires, what it catches, and what it misses will save you from assuming your site is protected when it isn’t.

    What triggers an automatic rollback

    WordPress monitors plugin updates for fatal PHP errors during a brief window immediately after activation. If a newly updated plugin throws a fatal error that would break your site, WordPress detects it during the next loopback request—a self-ping the system uses to verify the site is still responding.

    If that loopback fails, WordPress rolls the plugin back to its previous version and sends an email to the site admin address on file. The entire process happens within seconds to minutes, depending on how quickly the loopback request completes.

    Three conditions must be true for rollback to fire:

    • The plugin was updated through the WordPress admin dashboard or WP-CLI with the --defer-site-health flag
    • The fatal error occurs during plugin load or initialization—not later during a page request
    • The loopback request detects the failure before the request timeout (default: 10 seconds)

    If you update via SFTP, the rollback system never sees the change. If the plugin loads fine but crashes when a visitor hits a specific page, rollback won’t catch it. If your server is slow and the loopback times out before detecting the error, you’re on your own.

    What gets reversed, what doesn’t

    Rollback restores the plugin’s PHP files to the previous version. That’s it. Any database changes the new version made—schema migrations, new rows, updated option values—stay in place.

    This creates a mismatch problem. If version 2.0 of a plugin adds a database column and version 1.9 expects it not to exist, rolling back the code doesn’t undo the schema change. You’re now running old code against a new database structure.

    Most well-coded plugins handle this gracefully by checking for the existence of columns or tables before querying them. Poorly coded plugins assume the database structure matches the code version and break in new ways after rollback.

    Settings changes are similarly sticky. If the new version migrated your settings array to a new format, rollback won’t revert that migration. You’ll need to manually restore settings from a backup or reconfigure the plugin.

    When rollback fails silently

    Rollback depends on the loopback request succeeding. If your server blocks loopback requests—common on shared hosts with aggressive firewall rules or when using localhost SSL certificates—WordPress can’t verify the site is broken, so it won’t roll back.

    You can test whether loopbacks work by visiting Tools > Site Health in the WordPress admin. If the “Loopback request” test fails, automatic rollback won’t work either. Fixing it usually requires whitelisting your own domain in your firewall or adjusting WP_HTTP_BLOCK_EXTERNAL settings.

    Rollback also won’t fire if the fatal error occurs outside the plugin’s initialization phase. If a plugin loads successfully but crashes when you try to access its settings page, that’s a runtime error, not an initialization failure. WordPress considers the update successful because the site didn’t break immediately.

    Manual rollback as fallback

    Even when automatic rollback works, it’s worth knowing how to roll back manually. WordPress doesn’t keep old plugin versions on your server—it deletes them after update. To manually revert, you’ll need to download the previous version from the WordPress.org plugin repository.

    Visit wordpress.org/plugins/[plugin-slug]/advanced/ to access the developer view, which lists all previous versions. Download the version you need, delete the current plugin folder via SFTP or your host’s file manager, and upload the old version. Reactivate if necessary.

    If you’re on a managed WordPress host, check whether they offer automatic snapshots before updates. Kinsta, WP Engine, and similar hosts take filesystem snapshots before applying updates, letting you restore the entire plugin folder—and sometimes the database state—from before the update.

    For operators managing multiple sites, a staging environment is the better insurance. Test plugin updates on staging first. If they break, your production site never sees the bad code. If automatic rollback is your only safety net, you’re relying on a system that only catches a subset of failures.

    One Two Three Send covers WordPress operations, hosting, and the tools solo operators use to keep sites running. Subscribe for one operator-focused article every day.

  • WordPress REST API rate limiting: how plugins enforce request caps

    WordPress REST API rate limiting: how plugins enforce request caps

    WordPress REST API rate limiting: how plugins enforce request caps
    Photo by Stephen Phillips – Hostreviews.co.uk on Unsplash

    If you run a WordPress site with any meaningful traffic—or if you’ve built automation that hits your own API—you’ve probably seen a 429 error at some point. The WordPress REST API doesn’t enforce rate limits by default, but most production sites end up with them anyway, often without realizing it.

    The limits come from security plugins, caching layers, or server-level middleware. They’re rarely documented in dashboards, and they trigger differently depending on whether the request is authenticated, what endpoint you’re hitting, and whether the plugin treats logged-in users differently from unauthenticated bots.

    Here’s how the most common rate-limiting mechanisms actually work, and what to check when legitimate requests start getting blocked.

    Where rate limits come from

    Core WordPress doesn’t rate-limit the REST API. It’s open by default. But most hosting providers and security plugins layer restrictions on top:

    • Security plugins like Wordfence, iThemes Security, and Sucuri impose per-IP request caps, usually between 10 and 60 requests per minute depending on the endpoint.
    • Hosting firewalls (Cloudflare, server-level mod_security rules, or host-specific WAFs) apply blanket limits to all /wp-json/ traffic, often as low as 5 requests per second.
    • Caching plugins (WP Rocket, LiteSpeed Cache) sometimes block repeat API calls as part of bot-protection rules, especially if the requests bypass the cache entirely.

    The problem: these tools don’t coordinate. A single legitimate automation script can trip multiple rate limiters at once, and the error messages rarely tell you which layer rejected the request.

    How plugins enforce caps

    Most WordPress security plugins use one of two methods:

    IP-based throttling: The plugin logs request timestamps per IP address. If you exceed the cap within the time window, it returns a 429 response. This breaks multi-user environments where users share an IP (office networks, VPNs). It also penalizes API clients that batch requests from a single origin.

    Endpoint-specific rules: Some plugins treat /wp-json/wp/v2/posts differently from /wp-json/custom-namespace/endpoint. Public read endpoints often get stricter limits than authenticated write endpoints, under the assumption that bots hammer public routes more aggressively. Wordfence, for example, applies a default 10-request-per-minute cap to unauthenticated API traffic, but allows 60 for logged-in users.

    The gotcha: if your automation uses application passwords or OAuth tokens, the plugin may still treat it as unauthenticated if the token isn’t passed correctly in the Authorization header. That drops you into the stricter bucket.

    What the 429 response doesn’t tell you

    When you hit a rate limit, WordPress returns 429 Too Many Requests. Sometimes you get a Retry-After header; often you don’t. The response body rarely names the plugin or layer that blocked you.

    To diagnose:

    • Check server access logs for the exact timestamp of the 429. If the response came from Apache or Nginx (not PHP), it’s a server-level firewall rule, not a plugin.
    • Temporarily disable security plugins one at a time. If the 429 disappears, you’ve found the source.
    • Look for rate-limit settings in your security plugin’s advanced options. They’re rarely on the main dashboard—Wordfence buries them under “Firewall Options,” iThemes under “Advanced Settings.”

    If you’re using Cloudflare, check the Security Events log. Cloudflare’s “Rate Limiting” rules fire independently of WordPress, and they don’t show up in your WordPress error logs at all.

    When to raise limits (and when not to)

    If you’re hitting limits with your own automation, the fix is straightforward: whitelist your script’s IP or user agent in the security plugin’s settings. Most plugins let you exempt specific IPs or authenticated users from throttling.

    If public traffic is hitting limits, don’t just raise the cap. Rate limits exist to block credential-stuffing attacks and brute-force POST spam. Instead:

    • Cache aggressively. If bots are hammering /wp-json/wp/v2/posts, serve that response from Redis or a CDN edge. The request never hits PHP, so it doesn’t count against your rate limit.
    • Require authentication for write endpoints. If your API accepts user-submitted data, force clients to authenticate. Unauthenticated POST requests should return 401, not 200.
    • Monitor which endpoints get hit. If 90% of your API traffic goes to a single route, that’s either a bot or a misconfigured frontend polling too frequently. Fix the source, not the symptom.

    Testing your own limits

    Most operators don’t know their rate limits until they hit them in production. To test:

    Use a simple loop to hit your API 100 times in 60 seconds. Start with an unauthenticated GET request to /wp-json/wp/v2/posts. Note when the first 429 appears, then repeat with an authenticated request using an application password.

    If the limits differ, your plugin treats auth and non-auth traffic separately. If they’re the same, you’re hitting a server-level cap that doesn’t distinguish between the two.

    Most hosting providers don’t advertise their API rate limits. If you’re on managed WordPress hosting, check the knowledge base or open a ticket. The limit is usually there—it’s just not on the pricing page.

    Want more infrastructure breakdowns like this? Subscribe to One Two Three Send—we dig into the mechanics other newsletters skip.

  • WordPress page builder bloat: what 3MB of inline CSS actually does

    WordPress page builder bloat: what 3MB of inline CSS actually does

    WordPress page builder bloat: what 3MB of inline CSS actually does
    Photo by Team Nocoloco on Unsplash

    Open the source code of a site built with Elementor, Divi, or WPBakery and you’ll often find 2,000–4,000 lines of CSS sitting inline in the <head>. Not linked. Not cached. Just dropped into the HTML on every single page load.

    That’s not a configuration mistake. It’s how most WordPress page builders work by default. And for solo operators running content sites, it’s one of the biggest hidden performance taxes you’re paying.

    What inline CSS actually costs

    A typical Elementor page with moderate styling generates around 250–400 KB of inline CSS. Divi can hit 500 KB. WPBakery sits somewhere in between. None of that is cached by the browser the way an external stylesheet would be.

    The performance hit shows up in three places:

    • Time to First Byte (TTFB): WordPress has to generate all that CSS server-side before it can send the page. On shared hosting or underpowered VPS instances, that adds 200–600ms.
    • Render blocking: Browsers can’t start painting the page until they’ve parsed all the CSS in the <head>. Inline styles delay First Contentful Paint (FCP) by forcing sequential processing.
    • Bandwidth waste: Every page view re-downloads styles that could have been cached once and reused across the entire site.

    Run a Lighthouse test on a page builder site and you’ll see the same warnings: “Reduce unused CSS,” “Eliminate render-blocking resources,” “Reduce server response times.” All symptoms of the same root cause.

    Why builders do this (and when it’s justified)

    Page builders inject inline CSS because it’s the only way to guarantee per-page design flexibility without managing a complex cascade of stylesheets. If every page can have unique column widths, custom font weights, and individual color overrides, bundling all possible styles into one external file doesn’t work—you’d ship megabytes of unused rules.

    For marketing sites, landing pages, or portfolio sites where design variety matters more than repeat-visitor performance, that trade-off makes sense. A freelancer showcasing client work doesn’t care if every project page regenerates its CSS—visitors rarely see more than one.

    But for content-driven businesses—blogs, newsletters, membership sites—the math flips. Your readers visit multiple pages per session. They come back weekly. And you’re competing on page speed in both Google’s Core Web Vitals rankings and reader patience.

    The fix: CSS print method and selective loading

    Most page builders have a setting buried in performance options called something like “CSS Print Method” or “CSS Loading Method.” The default is usually “Internal” or “Inline.” Switch it to “External” and the builder will write styles to a static file instead.

    In Elementor, it’s under Elementor → Settings → Features → Optimized CSS Loading. Enable it, clear your cache, and check the source again. You’ll see a <link> tag instead of a <style> block. That file gets cached by the browser and reused across every page visit.

    Divi has a similar toggle under Divi → Theme Options → Builder → Advanced → Static CSS File Generation. WPBakery requires a plugin or manual filter hook, but the principle is identical.

    One gotcha: external CSS files can break if your caching plugin and your page builder both try to manage them. If you enable external CSS and suddenly see unstyled pages, check for cache conflicts. Purge everything, disable asset optimization in your caching plugin temporarily, and re-test.

    When to skip the builder entirely

    If your site is 80% blog posts and 20% custom landing pages, you don’t need a page builder site-wide. Use the default block editor (Gutenberg) for posts and only activate the builder on pages where you need layout control.

    Most builders let you disable their CSS output on post types you’re not using. In Elementor, go to Settings → General and uncheck “Post” under “Post Types.” Your blog posts shed hundreds of kilobytes instantly.

    For operators running membership content, course platforms, or newsletter archives, this is the fastest performance win available. A plain-text post with a featured image and a couple of headings doesn’t need a visual builder. It needs fast delivery and readable typography.

    If you’re launching a new site in 2026 and content is the core product, start with a lightweight theme like GeneratePress or Kadence and only add a page builder if you hit a layout you can’t solve with blocks. You’ll save yourself months of performance firefighting later.

    Got a page builder performance question or a caching conflict you can’t solve? Hit reply—we’re collecting builder/host combinations that reliably break, and we’ll cover them in a future piece.