> wp-performance-backend

WordPress backend performance optimization — profiling, queries, object cache, autoload, cron, and remote HTTP. Always-active rules when investigating slowness issues.

fetch
$curl "https://skillshub.wtf/alessioarzenton/claude-code-wp-toolkit/wp-performance-backend?format=md"
SKILL.mdwp-performance-backend

WordPress Backend Performance

Skill for diagnosing and resolving backend performance issues in WordPress 6.x+. Complementary to the performance skill (which covers frontend/Core Web Vitals). This one focuses on: server-side profiling, database queries, object cache, autoload options, cron, and remote HTTP calls.

When to use

Apply this skill when:

  • A page, REST endpoint, or admin screen is slow (high TTFB)
  • A profiling plan and tool recommendations are needed
  • Optimizing DB queries, autoloaded options, object cache, cron tasks, or remote HTTP calls
  • The symptom is intermittent or tied to logged-in vs anonymous users

Do not use for: frontend asset optimization (CSS/JS/images), Core Web Vitals, lazy loading — see the performance skill.

Prerequisites

Before starting, verify:

  • Environment: dev, staging, or production — and whether you can make changes (install plugins, change config)
  • How to target the installation: WordPress root path (--path=<path> for WP-CLI)
  • Symptom: which URL/REST route/admin screen is slow, when it happens (always vs sporadic, logged-in vs anonymous)
  • WP-CLI available: required for advanced profiling commands

Procedure

0) Guardrails — measure first, avoid risky operations

  1. Confirm whether you can perform write operations (install plugins, change config, flush cache)
  2. Identify a reproducible target (URL or REST route) and capture a baseline:
# Baseline with curl (TTFB).
curl -o /dev/null -s -w "TTFB: %{time_starttransfer}s\nTotal: %{time_total}s\n" "https://esempio.test/bandi/"

# With WP-CLI profile (if available).
wp profile stage --url="https://esempio.test/bandi/"
  1. Do not enable SAVEQUERIES or WP_DEBUG in production without explicit approval — they cause significant overhead

1) Quick diagnostics (before deep profiling)

If you have WP-CLI with the wp doctor plugin:

# Quick check — identifies common issues.
wp doctor check

# Issues it detects:
# - Autoload bloat (autoloaded options > 1MB)
# - SAVEQUERIES/WP_DEBUG active in production
# - Too many active plugins
# - Core/plugin/theme updates available

If you don't have wp doctor, check manually:

# Autoload size.
wp db query "SELECT SUM(LENGTH(option_value)) as total FROM $(wp db prefix)options WHERE autoload = 'yes'" --skip-column-names

# Active plugin count.
wp plugin list --status=active --format=count

# WP and PHP version.
wp core version && wp eval "echo PHP_VERSION;"

2) Deep profiling

Order of preference:

A) WP-CLI Profile (without browser)

# Where does the time go? (bootstrap / main_query / template).
wp profile stage --url="https://esempio.test/bandi/"

# Which hooks are slow?
wp profile hook --url="https://esempio.test/bandi/" --spotlight

# Test a specific code path.
wp profile eval 'get_posts(["post_type" => "bando", "posts_per_page" => 50]);'

B) Query Monitor (headless usage via REST)

Query Monitor can be used without a browser via REST response headers:

# Authenticate and inspect x-qm-* headers.
curl -s -D - "https://esempio.test/wp-json/wp/v2/posts" \
  -H "Authorization: Basic $(echo -n 'user:app-password' | base64)" \
  | grep -i 'x-qm'

C) Xdebug/XHProf + Blackfire

For PHP-level profiling — requires server configuration. Coordinate with hosting/DevOps.

3) Fixes by category (address the dominant bottleneck)

Use the profiling output to choose one main category:

A) Database queries

Common issues:

ProblemDiagnosisSolution
Too many queriesQuery count > 50 per pageIdentify N+1 patterns, use update_meta_cache()
Slow queriesIndividual queries > 100msAdd indexes, avoid complex meta_query
Expensive meta_queryLIKE or NOT EXISTS on postmetaConsider custom taxonomy instead of meta
N+1 on post metaget_post_meta() in loop without pre-cacheUse update_postmeta_cache() before the loop
// BEFORE — N+1 pattern (1 query for each post in the loop).
foreach ($posts as $post) {
    $stato = get_post_meta($post->ID, 'stato_bando', true);
}

// AFTER — Pre-cache + loop (2 total queries).
update_postmeta_cache(wp_list_pluck($posts, 'ID'));
foreach ($posts as $post) {
    $stato = get_post_meta($post->ID, 'stato_bando', true);
}

B) Autoload options

Options with autoload = 'yes' are loaded on every request:

# Identify the largest autoloaded options.
wp db query "SELECT option_name, LENGTH(option_value) as size FROM $(wp db prefix)options WHERE autoload = 'yes' ORDER BY size DESC LIMIT 20"
  • If you find blobs > 100KB: consider disabling autoload or moving the data
  • Plugins like rewrite_rules, active_plugins, widget_* are normally autoloaded
// Disable autoload for a heavy option.
wp_set_option_autoload('mia_opzione_pesante', false);

C) Object cache

If there is no persistent object cache (Redis, Memcached), the cache is in-memory only for the single request:

# Check if an object cache drop-in exists.
wp eval "echo file_exists(WP_CONTENT_DIR . '/object-cache.php') ? 'YES' : 'NO';"
  • Without persistent cache: every request re-runs all queries -> high impact on high-traffic sites
  • With persistent cache: verify hit rate and key sizes
// Use wp_cache for data that is expensive to compute.
$risultati = wp_cache_get('bandi_attivi_conteggio', '{{TEXT_DOMAIN}}');
if (false === $risultati) {
    $risultati = calcola_bandi_attivi();
    wp_cache_set('bandi_attivi_conteggio', $risultati, '{{TEXT_DOMAIN}}', HOUR_IN_SECONDS);
}

D) Remote HTTP calls

Calls to external APIs during page rendering are a performance killer:

  • Add short timeouts: 'timeout' => 5 (WordPress default: 5s, but some plugins increase it)
  • Cache the responses with transients:
function get_dati_esterni(): array {
    $cached = get_transient('dati_api_esterna');
    if (false !== $cached) {
        return $cached;
    }

    $response = wp_remote_get('https://api.esempio.it/dati', ['timeout' => 5]);
    if (is_wp_error($response)) {
        return []; // Graceful fallback.
    }

    $dati = json_decode(wp_remote_retrieve_body($response), true);
    set_transient('dati_api_esterna', $dati, HOUR_IN_SECONDS);
    return $dati;
}
  • Never call external APIs in a loop — use batch or a single request

E) Cron

# List scheduled cron events.
wp cron event list

# Run a single event for debugging.
wp cron event run mio_evento_cron
  • Reduce "due now" event spikes — distribute the schedules
  • Avoid cron tasks that do heavy work in the HTTP request path
  • For long-running tasks: consider wp cron event run from the system (crontab) instead of wp-cron.php

4) Verification (repeat the same measurement)

# Same measurement as the baseline.
curl -o /dev/null -s -w "TTFB: %{time_starttransfer}s\nTotal: %{time_total}s\n" "https://esempio.test/bandi/"

# Or with WP-CLI.
wp profile stage --url="https://esempio.test/bandi/"
  • Compare numbers before/after (same environment, same URL)
  • Verify that functional behavior has not changed
  • If the fix is risky, release behind a feature flag or gradually

WordPress 6.9 — Performance improvements

Keep these changes in mind when profiling:

  • On-demand CSS for classic themes: classic themes now load CSS only for blocks used on the page (previously only block themes). CSS payload reduction of 30-65%
  • Block themes without render-blocking assets: themes like Twenty Twenty-Four can load with zero blocking CSS (inline styles from theme.json)
  • Increased CSS inline threshold: more small stylesheets are inlined, reducing blocking requests

Verification checklist

  • Baseline and post-fix measurements captured (same environment, same URL)
  • wp doctor check clean (or improved) if available
  • No new PHP errors or warnings in the logs
  • No cache flush needed for correctness (flushing should be a last resort)
  • Functional behavior unchanged after optimization
  • Query count reduced (goal: < 50 for a standard page)

Common errors and solutions

ProblemLikely causeSolution
No improvement after the fixMeasured different URL/site, cache masking the result, stale opcode cacheVerify --url targeting, flush OPcache, repeat
Noisy profiling dataBackground tasks, cold caches, few samplesEliminate variables, test with warm caches, take more measurements
SAVEQUERIES causes overheadEnabled in productionDisable immediately — use only in dev/staging
Object cache not workingMissing drop-in or Redis/Memcached server downVerify object-cache.php and cache server status

What NOT to do

  • Do not optimize without measuring first — profiling comes before the fix
  • Do not enable SAVEQUERIES or WP_DEBUG in production without approval
  • Do not install plugins or run load tests in production without coordination
  • Do not flush cache during traffic without reason
  • Do not add MySQL indexes blindly — verify with EXPLAIN first
  • Do not cache everything indiscriminately — invalid cache is worse than no cache

┌ stats

installs/wk0
░░░░░░░░░░
first seenMar 17, 2026
└────────────

┌ repo

alessioarzenton/claude-code-wp-toolkit
by alessioarzenton
└────────────

┌ tags

└────────────