Category: Hosting

  • WordPress lazy loading delays images differently on mobile vs desktop

    WordPress lazy loading delays images differently on mobile vs desktop

    WordPress lazy loading delays images differently on mobile vs desktop
    Photo by Stephen Phillips – Hostreviews.co.uk on Unsplash

    WordPress enabled native lazy loading by default in 2020, and most operators never think about it again. The browser handles it, images load as users scroll, and Core Web Vitals improve. Simple.

    Except lazy loading doesn’t work the same way on mobile and desktop. The threshold that triggers an image to start loading—called the “distance-from-viewport” threshold—varies by browser, device type, and connection speed. That inconsistency affects how fast your site feels, even when the technical metrics look fine.

    How lazy loading thresholds actually work

    When WordPress adds loading="lazy" to an image, it’s telling the browser: don’t load this until the user is about to see it. But “about to see it” isn’t a fixed distance.

    Chrome and Edge use a threshold of roughly 1250 pixels on desktop with a fast connection. That means an image starts loading when it’s still more than a full screen away. On mobile, that threshold drops to around 2500 pixels—proportionally much larger relative to viewport height, but still about two to three scroll lengths.

    On slower connections (defined by the browser’s effectiveType API), those thresholds shrink. Chrome drops to around 2500 pixels on desktop and 1250 pixels on mobile when it detects 3G speeds.

    Firefox uses similar logic but calculates thresholds as a multiplier of viewport height rather than fixed pixels. Safari’s implementation is more conservative across the board.

    The result: the same page can trigger image loads at wildly different scroll positions depending on device and connection. A hero image that loads instantly on desktop might not even start fetching until a mobile user scrolls halfway down the page.

    Where this breaks perceived performance

    Lazy loading improves Largest Contentful Paint (LCP) and Total Blocking Time by deferring offscreen images. But it can hurt perceived speed if critical above-the-fold images get lazy-loaded by mistake.

    WordPress excludes the first image in the content from lazy loading, but that heuristic fails when:

    • Your hero image is a background image in CSS, not an <img> tag
    • Your layout uses a sidebar, and the “first” image is a small thumbnail in the sidebar, not the main content image
    • You’re using a page builder that injects images via JavaScript after initial render

    On mobile, where thresholds are proportionally larger, users scroll faster and notice the delay more. An image that starts loading at 2500 pixels might not finish rendering before the user reaches it, especially on slower devices or connections.

    This is why some sites feel faster on desktop even when mobile scores higher in Lighthouse. The lazy loading threshold mismatch creates a perception gap that metrics don’t fully capture.

    How to adjust lazy loading behavior

    You can’t directly control browser thresholds, but you can control which images get the loading="lazy" attribute.

    The simplest fix: remove lazy loading from above-the-fold images. WordPress provides a filter to exclude specific images:

    add_filter( 'wp_lazy_loading_enabled', '__return_false' );

    That disables it globally, which is overkill. Instead, target specific images by hooking into wp_get_attachment_image_attributes and removing the loading attribute for images in the first few blocks or above a certain position in the DOM.

    Most page builders and themes offer a “disable lazy loading” toggle for hero sections. Use it. The performance cost of eagerly loading one or two large images is trivial compared to the perceived-speed hit of lazy-loading them incorrectly.

    For mobile-specific tuning, consider using the fetchpriority="high" attribute on your primary hero image. It’s supported in Chrome, Edge, and Safari, and it tells the browser to prioritize that image even if lazy loading would otherwise delay it. WordPress doesn’t add this by default, but you can inject it via the same attribute filter.

    Test with real throttling, not just Lighthouse

    Lighthouse simulates a slow connection, but it doesn’t show you the scroll-and-wait behavior real users experience. Open Chrome DevTools, switch to the Network tab, and throttle to “Slow 3G.” Then actually scroll your site on a mobile viewport.

    Watch where images start loading. If they’re popping in after you’ve scrolled past them, your thresholds are too conservative or your images are too large. If they’re all loading at once on page load, you’re not benefiting from lazy loading at all.

    Run the same test on desktop. The behavior should be noticeably different. If it’s not, you might have a plugin overriding WordPress’s native implementation with a JavaScript-based lazy loader—those usually use fixed thresholds regardless of device.

    Most solo operators optimize for Lighthouse scores and call it done. But perceived speed matters more than any single metric, and lazy loading thresholds are one of the few variables that affect perception without showing up in reports.

    Reply with the laziest-loading image you’ve ever encountered. We’re collecting examples for a follow-up piece on edge cases that break WordPress’s heuristics.

  • WordPress page caching: when dynamic content breaks static delivery

    WordPress page caching: when dynamic content breaks static delivery

    WordPress page caching: when dynamic content breaks static delivery
    Photo: Rami via Wikimedia Commons (Public domain)

    Page caching is one of the fastest ways to speed up a WordPress site. Instead of rebuilding every page on every visit, the server delivers a static HTML snapshot. For content-driven sites, it’s a performance multiplier—until it breaks something you didn’t realize was dynamic.

    The promise is simple: cache the page once, serve it to everyone. The problem is that “everyone” includes logged-in users, people in different time zones, and visitors who should see personalized content. When you turn on full-page caching without understanding what gets frozen, you end up serving stale data to the wrong people.

    What page caching actually does

    Most caching plugins—WP Rocket, LiteSpeed Cache, W3 Total Cache—generate a static HTML file the first time someone visits a page. Every subsequent visitor gets that file until it expires or you manually clear the cache. The PHP engine never runs. The database never gets queried. It’s fast because it skips WordPress entirely.

    That works fine for a blog post that doesn’t change. It breaks the moment you add anything that should look different for different visitors: a “Welcome back, [Name]” message, a shopping cart count, a members-only section, a countdown timer, or even a “currently online” widget.

    The static HTML file doesn’t know who’s visiting. It shows the same cached snapshot to everyone—logged in or not, subscriber or guest, New York or Tokyo.

    What breaks first

    The most common casualties are widgets and sidebar elements that rely on server-side state. A “recent comments” block might show the same five comments for hours, even as new ones come in. A “popular posts” widget freezes at whatever was popular when the cache was built. If you’re running ads or affiliate content that rotates server-side, the same banner gets served to everyone until the cache clears.

    Logged-in users see the logged-out version of the page unless you explicitly exclude them from caching. That means no admin bar, no edit links, no personalized menus. If you’re running a membership site or a course platform, this is a deal-breaker. Members land on a page that tells them to log in, even though they already are.

    Checkout pages, account dashboards, and cart views also break under full-page caching. Most plugins auto-exclude /cart/ and /checkout/ paths, but if you’re using a custom URL structure or a non-standard plugin, you need to add exclusions manually.

    How to cache without breaking dynamic content

    The cleanest fix is to separate static and dynamic elements. Cache the page structure—header, footer, main content—and load personalized pieces via JavaScript after the page renders. Most modern membership plugins and ecommerce platforms do this by default: the page loads instantly from cache, then an AJAX call fetches the cart count or user name and injects it into the DOM.

    If you’re building custom features, use the same pattern. Cache the full page, but add a data-user-id attribute to any personalized container and populate it client-side with a lightweight API call. Keep that endpoint uncached—exclude /wp-json/ routes or use a dedicated AJAX handler.

    For logged-in users, most caching plugins offer a “don’t cache for logged-in users” toggle. Enable it if your site has frequent admin activity or if subscribers expect personalized views. The trade-off is that logged-in traffic bypasses the cache entirely, so page speed drops for those visitors. If you have a small team and most traffic is anonymous, this is fine. If half your visitors are logged in, you’re losing most of the performance benefit.

    Time-sensitive content—countdowns, limited offers, live event notices—should either be excluded from caching or rendered client-side. A cached page with a countdown timer will show the same time to everyone until the cache expires, which defeats the urgency. Use JavaScript to calculate the time difference in the browser, or exclude the page from caching and accept the performance hit.

    Testing what actually gets cached

    The easiest way to catch caching issues before they go live: visit your site in an incognito window, clear the cache, reload the page, then log in and reload again. If the logged-in view looks identical to the logged-out view, something’s wrong. Check the page source—if you see on a logged-in page, you need to adjust your exclusion rules.

    Most caching plugins log which pages get cached and which get bypassed. WP Rocket and LiteSpeed Cache both show cache hit/miss stats in their dashboards. If a checkout page shows a cache hit, you know it’s misconfigured.

    For membership sites, test with a subscriber account. Log in, navigate to a members-only page, then open the same URL in a private window. If the private window shows the member content, the page was cached while you were logged in and is now being served to everyone. Add that path to your exclusion list immediately.

    Page caching is worth the setup cost—most WordPress sites see 40–60% faster load times once it’s configured correctly. But if you ship personalized content, ecommerce, or logged-in experiences, the default settings will break something. Test every dynamic element, exclude what needs to stay fresh, and move the rest to client-side rendering.

    Got a caching setup that works for your WordPress site? Reply and let us know which plugin you’re using and how you handle logged-in users—we’ll feature the best setups in a future roundup.

  • WordPress REST API authentication: tokens vs. cookies vs. nonces

    WordPress REST API authentication: tokens vs. cookies vs. nonces

    WordPress REST API authentication: tokens vs. cookies vs. nonces
    Photo: Bilal Elmoussaoui and Authenticator contributors via Wikimedia Commons (GPLv3)

    If you’re building custom workflows on top of WordPress—feeding published posts into a social scheduler, syncing custom fields to an external CRM, or triggering email sends when a post goes live—you’re probably using the REST API. And authentication is where most operators hit a wall.

    WordPress offers three main authentication methods for REST API requests: application passwords (tokens), cookie authentication, and nonces. Each behaves differently, and choosing the wrong one means your automation fails silently or exposes your site to unnecessary risk.

    Application passwords: the safe default for external tools

    Application passwords were added to WordPress core in version 5.6. They’re revocable tokens tied to a specific user account, designed for external applications that need programmatic access without exposing your actual login password.

    When you generate an application password from your user profile, WordPress creates a 24-character token. You pass it via HTTP Basic Auth in every API request. If your workflow tool gets compromised or you stop using it, you revoke the token—your main password stays intact.

    This is the right choice for any tool that lives outside WordPress: Zapier workflows, custom Node.js scripts, Python automation, or third-party SaaS that needs to read or write data. The token can’t be used to log into the WordPress admin, only to authenticate API calls.

    Non-obvious gotcha: Application passwords only work over HTTPS. If your staging site uses HTTP, authentication will fail with a vague 401 error. WordPress blocks the feature entirely on non-encrypted connections.

    Cookie authentication: built-in, but brittle for automation

    Cookie authentication is what WordPress uses when you’re logged into the admin and browse the site. Your session cookie proves who you are. The REST API respects that cookie, so any JavaScript running on your own site—inside the WordPress admin or on the front end—can make authenticated requests without extra setup.

    This works fine for plugins or custom admin dashboards. But it’s unreliable for external automation. Cookies expire. They don’t travel well across domains. And if you’re running a headless setup or calling the API from a server, cookies don’t exist.

    Cookie-based requests also require a valid nonce for any write operation (POST, PUT, DELETE). The nonce is a time-limited token WordPress generates to prevent cross-site request forgery. It’s automatically included in admin-area JavaScript via wp_localize_script(), but if you’re building a custom front-end interface, you need to fetch and attach it manually.

    When to use it: Custom admin-area tools, React-based dashboards embedded in WordPress, or AJAX requests from logged-in users on the front end. Not for cron jobs, external services, or anything that runs without an active browser session.

    Nonces: not authentication, just anti-forgery

    Nonces are often confused with authentication, but they’re not. A nonce proves a request came from your site—not from a malicious third-party form. It doesn’t prove who sent the request; it just checks that the request originated from a legitimate WordPress-generated page.

    Nonces expire after 24 hours by default (technically 12–24 hours depending on when they were generated). If your automation fetches a nonce and then waits two days to use it, the request fails.

    You generate a nonce in PHP with wp_create_nonce('action-name') and validate it with wp_verify_nonce(). The REST API checks nonces automatically when you use cookie authentication, but only for destructive operations. Read-only GET requests don’t need one.

    Common mistake: Hardcoding a nonce into a JavaScript file. It expires, and your AJAX calls start failing silently. Always generate nonces dynamically and pass them to your script at page load.

    Which one to use

    If you’re calling the WordPress REST API from outside WordPress—Zapier, a headless front end, a Python script, a mobile app—use application passwords. Generate one per tool, label it clearly, and revoke it when you’re done.

    If you’re building a feature inside WordPress—a custom admin page, a front-end dashboard for logged-in users—use cookie authentication. WordPress handles the session for you. Just remember to include a nonce for write operations.

    If you’re passing data between WordPress and an external service that you control, consider setting up a custom endpoint with a shared secret instead of relying on user-based authentication. Store the secret in an environment variable, check it in your endpoint logic, and skip the user-permission overhead entirely.

    Most authentication failures in WordPress automations come from mixing these methods or assuming cookies work outside the browser. Pick the method that matches where your code runs, and half your API errors disappear.

    Got a WordPress automation question? Reply to this email—we cover one reader question every Sunday.

  • WordPress multisite subdomain DNS: how wildcard records actually work

    WordPress multisite in subdomain mode lets you spin up site1.example.com, site2.example.com, and so on—all from a single WordPress install. It’s powerful for niche site portfolios, client networks, or SaaS-style content platforms. But the DNS setup trips up even experienced operators, especially when subdomains don’t resolve or SSL certificates fail to provision.

    Here’s how the wildcard DNS record actually works, what propagation looks like in practice, and the edge cases that break automated SSL issuance.

    What the wildcard A record does

    When you configure WordPress multisite in subdomain mode, you add a single DNS record at your registrar or DNS provider:

    *.example.com A 203.0.113.45

    That asterisk is a wildcard. It tells DNS resolvers: “any subdomain that doesn’t have its own explicit record should point to this IP address.” So blog.example.com, shop.example.com, and anythingyouwant.example.com all resolve to the same server—your WordPress host.

    The WordPress application then inspects the Host header in each HTTP request and serves the correct site from its internal database. The DNS layer doesn’t know or care which subdomains exist; it just routes everything to the same place.

    Propagation timing and the root domain exception

    Wildcard DNS propagates like any other record—typically within minutes to a few hours, depending on TTL and resolver caching. But two gotchas appear frequently:

    The root domain doesn’t match the wildcard. If you have an existing A record for example.com pointing to a different IP (say, a marketing site on a separate host), that takes precedence. The wildcard only catches subdomains. If you want example.com itself to serve a multisite network site, you need a separate A record for the root, and it must point to the same IP as the wildcard.

    Explicit subdomain records override the wildcard. If you previously set up mail.example.com A 198.51.100.10 for an email service, that record wins. The wildcard only applies when no more-specific record exists. Audit your DNS zone file before enabling multisite—old staging subdomains or forgotten services can create confusing “site not found” errors.

    SSL certificate provisioning and wildcard complications

    Most managed WordPress hosts and CDNs (Cloudflare, Kinsta, WP Engine) offer automatic Let’s Encrypt SSL. But wildcard certificates require DNS-01 challenge validation, not the simpler HTTP-01 method.

    Here’s what that means in practice:

    • Single-site certificates use HTTP-01: Let’s Encrypt places a file at example.com/.well-known/acme-challenge/token, retrieves it, and issues the cert. Takes seconds.
    • Wildcard certificates use DNS-01: Let’s Encrypt asks you to create a TXT record at _acme-challenge.example.com, waits for propagation, validates it, then issues. This requires API access to your DNS provider, which not all hosts support automatically.

    If your host doesn’t support wildcard SSL automation, you have two options:

    1. Manually provision wildcard certs every 90 days (painful).
    2. Use a reverse proxy like Cloudflare in front of WordPress, letting Cloudflare handle wildcard SSL termination. Traffic flows: visitor → Cloudflare (SSL) → origin server (can be HTTP or a Cloudflare-issued origin cert).

    Cloudflare’s free tier includes wildcard SSL and works well for multisite operators who don’t need enterprise SLA guarantees. Just ensure SSL/TLS mode is set to “Full” or “Full (strict)”—”Flexible” mode (Cloudflare-to-visitor encrypted, Cloudflare-to-origin unencrypted) creates mixed-content warnings and breaks WordPress admin over HTTPS.

    When new subsites don’t resolve immediately

    You create a new subsite in WordPress, visit newsite.example.com, and get a DNS error. The wildcard’s already in place—what’s wrong?

    Two common causes:

    Local DNS cache. Your machine or router cached a previous NXDOMAIN (non-existent domain) response. Flush your local DNS cache (sudo dscacheutil -flushcache on macOS, ipconfig /flushdns on Windows) or wait 5–15 minutes.

    CAA records blocking SSL issuance. If you have a CAA record at the root domain restricting which certificate authorities can issue certs (e.g., example.com CAA 0 issue "letsencrypt.org"), and your host uses a different CA or expects wildcard issuance, the cert request fails silently. Check your DNS zone for CAA records if SSL won’t provision for new subsites.

    One non-obvious tip: use a staging wildcard on a separate domain

    If you’re testing multisite before going live, don’t use a subdomain of your production domain—use a completely separate domain or a .test suffix with local /etc/hosts entries. Why? Because once you add the wildcard A record to your live domain, every possible subdomain resolves, including ones you haven’t created yet. That can expose staging sites to search engines or curious visitors poking around common subdomain names like staging.example.com or dev.example.com.

    A safer pattern: register example-staging.com, apply the wildcard there, and test your network in isolation. When ready, migrate to the production domain with confidence that DNS and SSL won’t surprise you.

    Got a WordPress multisite setup question? Hit reply—we’d love to feature your scenario in a future Q&A piece.

  • WordPress maintenance mode locks out admins—here’s why

    WordPress maintenance mode locks out admins—here’s why

    WordPress maintenance mode locks out admins—here's why
    Photo by Mike Szczepanski on Unsplash

    You activate a WordPress plugin update, see the “briefly unavailable for scheduled maintenance” message, and refresh a minute later expecting your dashboard. Instead, you’re staring at a blank maintenance page—with no login link, no bypass, and no way back in.

    This isn’t a hypothetical edge case. Maintenance mode lockouts happen when a plugin update stalls, a server timeout interrupts file writes, or a third-party maintenance plugin misconfigures its access rules. The site enters maintenance mode, but the cleanup script that’s supposed to disable it never runs.

    Here’s what actually happens, when you’re at risk, and how to fix it without SSH or panic.

    What triggers WordPress maintenance mode

    WordPress core uses a file called .maintenance in your site’s root directory to signal that updates are in progress. When you trigger a plugin or theme update from the dashboard, WordPress writes this file, runs the update, then deletes the file. The entire process usually takes 10–30 seconds.

    If something interrupts that flow—a PHP timeout, a failed database query, a hosting firewall rule that kills long-running requests—the .maintenance file stays in place. WordPress reads it on every page load and shows the maintenance message instead of your site.

    Crucially, this affects admin URLs too. The maintenance check happens before WordPress loads wp-admin, so even /wp-login.php returns the same generic holding page. No login form. No dashboard. No escape hatch.

    When third-party maintenance plugins make it worse

    Plugins like WP Maintenance Mode and Coming Soon Page add their own maintenance layers. They write database options or create custom .maintenance files with extended logic. Some let you whitelist IP addresses or set a bypass secret; others don’t.

    The problem: if you activate one of these plugins and misconfigure the access rules—or if the plugin conflicts with a caching layer—you can lock yourself out without triggering a core WordPress update at all. The plugin’s maintenance screen overrides everything, including admin access, and you’re left guessing which file or database row is responsible.

    How to recover access in under five minutes

    If you’re locked out, you need file-level access. Most shared hosting control panels (cPanel, Plesk, Flywheel, BigScoots) include a file manager. Open it, navigate to your WordPress root directory (usually public_html or www), and look for .maintenance. Delete it. Refresh your site. You’re back in.

    If the lockout persists, a plugin is holding the maintenance state. Connect via SFTP or file manager and rename the plugin’s folder inside /wp-content/plugins/. WordPress will deactivate it automatically. If you don’t know which plugin caused it, rename the entire plugins folder to plugins-off, log back in, then rename it back and reactivate plugins one at a time.

    For sites using object caching (Redis, Memcached), flush the cache from your hosting dashboard or via WP-CLI. Cached maintenance flags sometimes outlive the .maintenance file itself.

    One non-obvious tip: set a cron failsafe

    If you run a solo site or small team operation, add a cron job that deletes .maintenance files older than five minutes. Most hosting control panels let you schedule shell commands. A one-liner like find /path/to/wordpress -name .maintenance -mmin +5 -delete prevents lockouts from becoming multi-hour emergencies.

    For managed WordPress hosts that don’t expose cron directly, some monitoring services (like UptimeRobot or Oh Dear) can trigger webhook alerts when your site returns a maintenance header for longer than expected. You won’t auto-recover, but you’ll know within minutes instead of discovering it when a reader emails you.

    Maintenance mode is a necessary safety mechanism, but it’s designed for automated workflows—not manual bailouts. The faster you recognize a lockout, the less downtime you’ll eat.

    Want more WordPress infrastructure breakdowns? Reply with the hosting gotcha that cost you the most downtime—we’ll cover it in a future issue.

  • WordPress object cache: when persistent caching breaks your site

    WordPress object cache: when persistent caching breaks your site

    WordPress object cache: when persistent caching breaks your site
    Photo by Luke Chesser on Unsplash

    Most WordPress performance guides tell you to enable persistent object caching with Redis or Memcached. The promise: fewer database queries, faster page loads, happier visitors. The reality: if you don’t understand what WordPress caches in memory—and what shouldn’t be cached—you’ll spend hours debugging stale data, broken admin screens, and plugins that stop working.

    Persistent object caching is a feature worth using, but only if your site actually needs it and your stack can handle the edge cases. Here’s how it works, when to turn it on, and the one non-obvious setting that prevents most breakage.

    What WordPress object caching actually does

    By default, WordPress caches objects—database query results, option lookups, taxonomy terms—in PHP memory for the duration of a single page load. Once the request ends, the cache clears. Every new visitor triggers fresh queries.

    Persistent object caching extends that lifetime. Instead of storing cached data in PHP’s runtime memory, WordPress writes it to Redis or Memcached, which live outside the PHP process. The cache survives across requests, across visitors, across server restarts (until you flush it or it expires).

    For high-traffic sites, this cuts database load dramatically. A single post might generate 50+ queries on an uncached load. With object caching, most of those queries hit Redis instead of MySQL. Page generation drops from 800ms to 200ms.

    For low-traffic sites—under 10,000 monthly visits—the difference is negligible. Your database isn’t the bottleneck; network latency and unoptimized images are. Adding Redis adds complexity without measurable speed gains.

    When persistent caching breaks things

    The most common failure mode: stale data that won’t refresh. You update a post, but the homepage still shows the old title. You change a user role, but the admin menu doesn’t reflect the new permissions. You deactivate a plugin, but its settings page still appears.

    This happens because WordPress—and many plugins—assume the object cache clears between requests. They write data once, cache it aggressively, and never invalidate it. When you enable persistent caching, that assumption breaks.

    WooCommerce is notorious for this. Product stock counts, cart totals, and session data get cached with long expiration times. If your cache invalidation logic doesn’t account for stock changes, customers see inaccurate inventory. The fix: WooCommerce-specific cache groups marked as non-persistent, or a plugin like Object Cache Pro that handles WooCommerce edge cases automatically.

    Membership and LMS plugins—LearnDash, MemberPress, Restrict Content Pro—cache user permissions and course progress. If the cache doesn’t flush when a user upgrades or completes a lesson, access control breaks. You’ll get support tickets claiming “I paid but still can’t access the content.”

    The second failure mode: cache key collisions in multisite or multi-environment setups. If your staging and production sites share the same Redis instance without unique key prefixes, updating content on staging can overwrite production cache entries. Your live site serves draft content or test data.

    The non-obvious setting: cache group exclusions

    Most object cache drop-ins—Redis Object Cache, Memcached Object Cache, Object Cache Pro—let you exclude specific cache groups from persistence. These groups fall back to runtime-only caching, just like default WordPress behavior.

    The groups you almost always want to exclude:

    • counts — post counts, comment counts, term counts. These change frequently and queries are cheap.
    • plugins — plugin metadata. Caching this causes deactivated plugins to linger in memory.
    • themes — same reason. Theme switches won’t take effect until you manually flush.
    • userlogins and userslugs — user session and authentication data. Caching these across requests creates security risks.

    If you’re running WooCommerce, also exclude:

    • wc_session_id
    • wc_cart_hash
    • wc_reserved_stock

    For membership plugins, exclude any group containing “user_meta” or “permissions” in the name. Check your plugin’s documentation—most maintain a list of recommended exclusions.

    In the Redis Object Cache plugin (the most popular free option), you set exclusions via the WP_REDIS_IGNORED_GROUPS constant in wp-config.php:

    define('WP_REDIS_IGNORED_GROUPS', ['counts', 'plugins', 'themes', 'userlogins', 'userslugs']);

    Object Cache Pro uses a similar constant: OCP_IGNORED_GROUPS.

    When to actually enable persistent caching

    Turn it on if:

    • Your site gets more than 50,000 pageviews per month, or you run resource-intensive queries (complex WP_Query loops, custom taxonomies with thousands of terms).
    • Your hosting plan includes Redis or Memcached as a managed service. Most managed WordPress hosts—Kinsta, WP Engine, Flywheel—provision Redis automatically. If you’re on shared hosting or a basic VPS, setting up Redis yourself adds maintenance overhead.
    • You can test on staging first and you have a cache flush strategy. Automatic purging on post updates is table stakes; you also need manual flush access for emergencies.

    Skip it if:

    • Your site is low-traffic or you’re already using a full-page cache (WP Rocket, LiteSpeed Cache, Cloudflare). Full-page caching delivers bigger performance wins with fewer edge cases.
    • You run a membership, e-commerce, or LMS site and you don’t have time to debug cache invalidation issues. The risk of serving stale user-specific data outweighs the speed benefit.
    • Your host doesn’t offer Redis, and you’re not comfortable managing it yourself. A misconfigured Redis instance—no password, exposed port, no memory limits—is a security and stability liability.

    Want more hosting and performance breakdowns like this? Subscribe to One Two Three Send—we dig into the infrastructure decisions solo operators actually face, no enterprise fluff.

    Persistent object caching works when your site’s traffic and query complexity justify it, and when you’ve accounted for the plugins that assume caches don’t persist. If you’re enabling it just because a checklist told you to, you’re adding risk without reward.

  • WordPress image optimization plugins: WebP vs. AVIF in 2026

    WordPress image optimization plugins: WebP vs. AVIF in 2026

    WordPress image optimization plugins: WebP vs. AVIF in 2026
    Photo by Growtika on Unsplash

    Image optimization plugins for WordPress promise faster page loads and better Core Web Vitals scores. Most now offer WebP conversion by default, and a growing number add AVIF support. But the two formats solve different problems, and choosing the wrong one can break images for a slice of your traffic or bloat your hosting bill.

    Here’s how to pick the right format—and when to use both.

    WebP: mature, compatible, predictable file sizes

    WebP has been around since 2010. Browser support hit critical mass years ago: Chrome, Firefox, Safari, and Edge all support it natively. According to Can I Use data, WebP coverage sits above 97% of global users in 2026.

    WebP handles both lossy and lossless compression. For most photography and blog post images, WebP lossy at 80–85 quality produces files 25–35% smaller than JPEG at equivalent visual quality. Lossless WebP beats PNG for screenshots and graphics with transparency, though the size advantage is smaller—usually 15–20%.

    The format supports alpha-channel transparency, which makes it a direct PNG replacement. If your site uses transparent logos, icons, or overlays, WebP won’t break them.

    Most WordPress image optimization plugins—ShortPixel, Imagify, Smush, EWWW Image Optimizer—convert to WebP automatically. The format is stable, well-tested, and won’t surprise you with edge cases.

    AVIF: smaller files, slower encoding, spottier support

    AVIF arrived later—Chrome added support in 2020, Safari in 2021. Browser coverage in 2026 hovers around 92%, which means 8% of visitors still can’t decode it. That gap includes older Android devices and some corporate-locked browsers.

    AVIF’s advantage is file size. At equivalent quality settings, AVIF images run 20–30% smaller than WebP and 40–50% smaller than JPEG. For image-heavy sites—portfolios, e-commerce galleries, photo blogs—that difference compounds fast.

    The tradeoff is encoding time. Generating AVIF files takes 3–5× longer than WebP on most hosting environments. If your plugin converts images on-upload or via cron job, AVIF processing can time out on shared hosting or budget VPS plans. Plugins like ShortPixel and Imagify offload conversion to their own servers, which sidesteps the timeout problem but adds API dependency.

    AVIF also handles transparency, but browser support for alpha-channel AVIF lags slightly behind opaque AVIF. If you’re converting transparent PNGs to AVIF, test rendering in Safari and Firefox before deploying site-wide.

    When to use WebP, AVIF, or both

    If your hosting plan is shared or low-tier VPS, stick with WebP. AVIF encoding can spike CPU usage and trigger resource limits. WebP delivers most of the file-size benefit without the processing cost.

    If you’re on managed WordPress hosting or a dedicated server with headroom, enable AVIF with WebP fallback. Most modern plugins support this: serve AVIF to browsers that accept it, WebP to the rest, and original JPEG/PNG as a last resort. The plugin detects support via the Accept HTTP header or generates a <picture> element with multiple <source> tags.

    For e-commerce sites with hundreds of product images, AVIF’s size advantage matters. A 30% reduction in image payload can shave a full second off Largest Contentful Paint on mobile. That translates to measurable conversion lift. Just make sure your plugin queues conversions in the background—batch-converting 500 images at once will lock up most hosting environments.

    For blogs with a few images per post, WebP is enough. The complexity of AVIF fallback chains and the 8% browser gap aren’t worth the incremental file-size win.

    One plugin detail that trips people up

    Most WordPress image optimization plugins store multiple formats of the same image—original JPEG, WebP, and AVIF—on disk. A single uploaded image can generate three or four files, depending on your settings. If you’re on a hosting plan with tight disk quotas, this adds up fast. A 2 GB media library can balloon to 5–6 GB after enabling both WebP and AVIF.

    Check your plugin’s settings for an option to delete originals after conversion. Some plugins keep the source file for rollback purposes; others let you purge it once the optimized versions are live. If disk space is tight, delete originals—but keep a local backup of your media library first.

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

  • WordPress REST API rate limits: what trips automated workflows

    WordPress REST API rate limits: what trips automated workflows

    WordPress REST API rate limits: what trips automated workflows
    Photo: Matinbeigi via Wikimedia Commons (CC BY-SA 4.0)

    If you’re running any kind of automated workflow that touches WordPress—content sync tools, headless front-ends, analytics dashboards, social schedulers—you’re making REST API requests. And sooner or later, you’ll hit a wall you didn’t know existed.

    Most managed WordPress hosts impose rate limits on REST API calls. The problem: they rarely document the exact threshold, and when you exceed it, the failure mode is rarely obvious. Your workflow just… stops working.

    Where the ceiling sits

    Rate limits vary wildly by host and plan tier. WP Engine enforces approximately 60 requests per minute per IP on mid-tier plans. Kinsta’s limit sits closer to 100 requests per minute but drops during peak traffic windows. SiteGround and Bluehost shared plans can throttle as low as 30 requests per minute under load.

    The limit isn’t always per-account—it’s often per-IP or per-site. If you’re running multiple automations from the same server (a Zapier workflow pulling post data while a headless front-end fetches metadata), both count toward the same ceiling.

    BigScoots and similar VPS-adjacent hosts give you more headroom, but you’re still sharing infrastructure unless you’ve negotiated custom limits or moved to dedicated resources.

    How you trip it without noticing

    The most common culprit: bulk operations during content imports or migrations. If you’re syncing 500 posts from an external CMS via REST API, and each post requires three API calls (create post, upload featured image, assign taxonomy terms), that’s 1,500 requests. At 60 requests per minute, you’re looking at 25 minutes—but only if nothing else touches the API during that window.

    Social scheduling tools that auto-pull post excerpts or featured images can silently hammer the API every time they refresh your queue. Analytics plugins that log every page view via REST API can push you over the edge during traffic spikes.

    Headless WordPress setups are especially vulnerable. If your Next.js or Gatsby build process fetches all posts, taxonomies, and media in parallel during deployment, you can blow through your limit in seconds. Incremental static regeneration helps, but only if you’ve tuned the request concurrency.

    How to test your actual limit

    Most hosts won’t tell you the number until you ask support directly—and even then, the answer is often vague (“we recommend staying under 60 requests per minute”). The fastest way to find your real ceiling: controlled load testing.

    Use a tool like curl with a loop, or a lightweight Node script with axios, to hit a non-destructive endpoint (/wp-json/wp/v2/posts?per_page=1) at increasing rates. Start at 30 requests per minute, then 60, then 100. Watch for HTTP 429 responses or sudden timeouts.

    Log the exact rate where you start seeing failures. That’s your ceiling. Build in a 20% buffer—if you hit 429 at 80 requests per minute, design your workflows to stay under 65.

    The non-obvious fix

    Most developers reach for caching or request batching first. Both help, but the real leverage is in request sequencing and backoff logic.

    If you’re building a custom integration, implement exponential backoff: when you hit a 429, wait two seconds, then four, then eight. Most WordPress hosts reset rate-limit counters every 60 seconds, so a brief pause often resolves the issue without killing your workflow.

    For third-party tools (Zapier, Make, n8n), check if they expose retry settings or rate-limit handling. Zapier’s “Delay After Queue” action lets you throttle outbound requests manually. Make’s HTTP module supports custom retry logic via error handlers.

    If you’re running a headless build process, switch from parallel to sequential fetching for high-volume endpoints, or split your build into smaller incremental chunks. Gatsby’s GATSBY_CONCURRENT_DOWNLOAD environment variable lets you cap parallel requests; start at 10 and tune down if you’re still hitting limits.

    One last thing: if you’re on a managed host and legitimately need higher limits for a production workflow, ask. Most hosts will raise the ceiling for established accounts with predictable traffic patterns—but only if you ask before you break something.

    Have a question about WordPress infrastructure or automation limits? Reply to this email—we cover reader questions every Sunday.

  • WordPress plugin auto-updates: when to enable and when to audit first

    WordPress plugin auto-updates: when to enable and when to audit first

    WordPress plugin auto-updates: when to enable and when to audit first
    Photo: Matinbeigi via Wikimedia Commons (CC BY-SA 4.0)

    WordPress added automatic plugin updates in 2020, but most solo operators still toggle them on or off by instinct. Some enable everything and hope for the best. Others disable all auto-updates and let security patches pile up for months.

    Both approaches fail eventually. A smarter strategy is to classify your plugins by risk, then set auto-update policies based on what breaks when something goes wrong.

    What auto-updates actually do

    When you enable auto-updates for a WordPress plugin, the core update routine checks twice daily for new versions. If a new release is available, WordPress downloads and activates it without asking. No email confirmation. No manual review.

    Minor updates—like going from version 3.4.1 to 3.4.2—usually contain security patches or bug fixes. Major updates—like 3.x to 4.0—often introduce new features, deprecate old functions, or rewrite significant chunks of code.

    WordPress doesn’t distinguish between the two when auto-updating. If the developer ships a major version, your site will install it overnight. That’s where problems start.

    The risk matrix: which plugins to auto-update

    Start by grouping your active plugins into three tiers based on what happens if they break.

    Tier 1: Critical path plugins. These handle forms, checkout flows, email capture, payment processing, or user authentication. If they fail, you lose subscribers or revenue. Examples: WooCommerce, Gravity Forms, MemberPress, any payment gateway integration.

    Never enable auto-updates for Tier 1 plugins. Test major updates in a staging environment first. Even minor updates can introduce conflicts with your theme or other plugins, and you won’t know until someone reports a broken checkout.

    Tier 2: Analytics and third-party integrations. These plugins connect WordPress to external services—Google Analytics, Facebook Pixel, ConvertKit, Zapier webhooks. They rarely touch core site functionality, but when they break, you lose tracking data or automation triggers.

    Enable auto-updates for security patches only if the plugin developer uses semantic versioning and maintains a public changelog. If the developer ships breaking changes without warning, disable auto-updates and check manually once a month.

    Tier 3: Cosmetic and convenience plugins. Syntax highlighters, table-of-contents generators, related-post widgets, social share buttons. These improve the reader experience but don’t affect conversions or data collection.

    Enable auto-updates. If something breaks, you’ll notice it during your next post preview. The risk is low, and staying current reduces the chance of a security exploit in an unmaintained codebase.

    The audit cadence nobody talks about

    Enabling auto-updates isn’t a one-time decision. Plugin developers change ownership, get acquired, or abandon projects. A plugin that was safe to auto-update in January might ship a disastrous update in August.

    Set a quarterly reminder to review your auto-update settings. Check each plugin’s changelog for the last three months. If you see phrases like “major refactor,” “breaking changes,” or “deprecated legacy support,” disable auto-updates and test manually before upgrading.

    Also watch for plugins that haven’t shipped an update in six months. That’s often a sign the developer has moved on. Disable auto-updates, find a replacement, and migrate before a WordPress core update introduces a fatal incompatibility.

    The staging environment exception

    If you run a staging site that mirrors production, you can auto-update aggressively there and manually promote updates only after confirming nothing broke. This works well for operators publishing daily or running membership sites where downtime is expensive.

    The catch: staging environments need to be true mirrors. Same theme, same plugins, same server PHP version, same database size. A lightweight staging site with dummy content won’t catch conflicts that only appear under load or with real user data.

    Most managed WordPress hosts—BigScoots, Kinsta, WP Engine—offer one-click staging environments that sync database and files from production. If your host doesn’t, the manual sync overhead usually outweighs the auto-update safety benefit.

    Want more infrastructure breakdowns like this? Subscribe to One Two Three Send for weekly operator-to-operator guides on the tools that actually run online businesses.

  • WordPress database backups: snapshot frequency vs. disk cost

    WordPress database backups: snapshot frequency vs. disk cost

    WordPress database backups: snapshot frequency vs. disk cost
    Photo by Woliul Hasan on Unsplash

    Most WordPress operators set a backup schedule once and forget it. Daily snapshots sound safe. Hourly feels safer. But backup frequency compounds fast—and if you’re running on managed WordPress hosting or a VPS, you’re paying for every copy stored.

    The question isn’t whether to back up. It’s how often you need to, what retention window actually protects you, and when snapshot frequency starts costing more than the risk it mitigates.

    Backup frequency changes your storage footprint

    A typical WordPress database for a content site with 500 posts, 2,000 comments, and standard plugin metadata runs between 15 MB and 50 MB. If you’re running WooCommerce, memberships, or forum software, double or triple that.

    Hourly backups with 30-day retention mean 720 snapshots per month. At 30 MB per snapshot, that’s 21 GB of backup storage. Most managed hosts allocate 10–20 GB of backup space before charging overage fees—usually $0.10 to $0.25 per GB per month.

    Switch to daily backups with the same retention, and you drop to 30 snapshots: 900 MB total. The difference is $2 to $5 per month for a small site, but $20+ if your database crosses 200 MB.

    Backup plugins like UpdraftPlus and BackWPup let you set different intervals for database vs. files. Your database changes every time someone comments, subscribes, or places an order. Your theme files don’t. Splitting the schedule—hourly database, weekly files—cuts storage costs without losing transaction-level recovery.

    Retention windows protect different failure modes

    Thirty-day retention is a convention, not a requirement. What you’re protecting against determines how far back you need to go.

    If you’re worried about a bad plugin update or a botched migration, you need snapshots from the last 48 hours. Anything older than a week is archaeological. A 7-day retention window with hourly backups gives you 168 recovery points and costs one-quarter the storage of a 30-day window.

    If you’re worried about silent data corruption—broken automation, spam injection, or a membership plugin quietly deleting records—you need longer retention, but you don’t need high frequency. Weekly backups kept for 90 days let you spot patterns and roll back months without paying for hundreds of hourly snapshots.

    The hybrid approach: hourly backups kept for 7 days, plus one weekly backup kept for 12 weeks. That’s roughly 180 snapshots instead of 720, and it covers both rapid rollback and long-term forensics.

    Incremental vs. full snapshots: what your plugin actually stores

    Not all backup plugins store full copies every time. Incremental backups save only the database rows that changed since the last snapshot. UpdraftPlus, BlogVault, and most enterprise WordPress backup tools support incremental mode.

    For a content site publishing twice a week, an incremental backup after the first full snapshot might be 500 KB instead of 30 MB. Over a month, that shrinks your storage footprint by 80% or more.

    The tradeoff: incremental backups depend on the chain. If snapshot #4 is corrupted, you can’t restore from snapshot #10 without it. Some plugins automatically store a weekly full backup alongside incrementals to break the dependency chain. Check your plugin’s settings—most don’t enable this by default.

    When to pay for more frequent backups

    If you’re processing transactions, taking payments, or running time-sensitive campaigns, hourly backups are worth the cost. Losing six hours of WooCommerce orders or sponsor sign-ups isn’t a storage-cost discussion—it’s a revenue-loss discussion.

    If you’re publishing a content site with no e-commerce and infrequent comments, daily backups are enough. Your exposure window is the time between backups, and for most solo operators, a day of republishing is tolerable compared to the compounding cost of storing 720 snapshots you’ll never use.

    One non-obvious tell: check your WordPress database activity log (if your host provides one, or install Query Monitor for a week). If 95% of your write queries happen during your publishing window—say, Tuesday and Thursday mornings—you don’t need constant snapshots. Schedule backups for right after you publish, and drop frequency the rest of the week.

    What breaks when you skimp

    The biggest failure mode isn’t missing a backup window. It’s discovering your backups don’t restore.

    Twice a year, test a restore to a staging environment. Download a snapshot, spin up a local WordPress instance or a staging server, and import it. If your backup plugin stores files and database separately, make sure both pieces actually reconnect. I’ve seen operators keep 90 days of database snapshots that restored to a blank site because the file backup was misconfigured.

    Second most common break: your backup plugin times out mid-snapshot because your host limits PHP execution to 60 seconds and your database takes 90 to export. This fails silently unless you check logs. Solution: switch to a plugin that chunks exports (UpdraftPlus and BackWPup both do this), or move to host-level backups if your provider offers them.

    Want more infrastructure breakdowns like this? Subscribe to One Two Three Send—we cover the WordPress tooling, hosting gotchas, and cost optimizations that solo operators actually run into.