WordPress best practices

Performance and stability best practices for WordPress on TurboStack, covering Varnish full-page cache, Redis object cache, OPcache, Nginx tuning and safe scaling.

A fast, stable WordPress site comes from layered caching, a tuned runtime and disciplined change management. On TurboStack, much of this is already configured for you when you deploy a wordpress app. Most of your work is enabling the right plugins and avoiding conflicts. This page covers what the platform configures and the optimizations worth adding on top.

What TurboStack configures for you

When you deploy WordPress, TurboStack provisions a working, production-shaped stack:

  • Nginx vhost tuned for WordPress - the document root is public_html. Pretty-permalink rewrites (try_files $uri $uri/ /index.php?$args) are in place. Static assets (js, css, images, ico) are served with expires max. xmlrpc.php is denied to reduce the attack surface.

  • PHP-FPM backend per site - PHP requests are passed to a dedicated FastCGI pool with a generous fastcgi_read_timeout (1200s) so long imports and updates do not time out.

  • Varnish full-page cache (optional) - when varnish_enabled is set, a WordPress-aware VCL is installed. It bypasses the cache for wp-admin, logged-in users, the WooCommerce cart/checkout/my-account flows, and wp-cron.php. It caches static files for a day, normalises query strings, and strips tracking parameters (utm_*, fbclid, gclid). It supports PURGE from the Proxy Cache Purge plugin.

  • wp-config.php - generated with your database credentials (localhost, dedicated DB and user), fresh secret-key salts and WP_DEBUG off for production.

  • Optional auto-install - with app_install: true, wp-cli installs core and sets permalinks to /%postname%/. It removes the default themes and the Akismet/Hello plugins, then installs and activates WP Super Cache.

  • Enable Redis object cache - turn on Redis as a service and install a Redis object-cache plugin (for example wp-redis). Repeated queries (options, transients, sessions) then hit memory instead of MySQL, which cuts database load - especially for WooCommerce and logged-in traffic. Point the plugin at the Redis socket in wp-config.php, then enable it:

    $redis_server = array( 'host' => '/var/run/redis/redis.sock', 'port' => null, 'database' => 1 );
    wp redis enable
    wp transient delete-all

    When several sites share one Redis server, give each a different database id so they do not overwrite each other's cache.

  • Use the Varnish full-page cache - enable varnish_enabled for content-heavy and marketing sites; install the Proxy Cache Purge plugin so edits purge the cache automatically. Validate compatibility before enabling on stores with heavy logged-in flows.

  • Keep one full-page cache - run a single full-page caching layer. If Varnish is on, prefer it and avoid a second page-cache plugin writing conflicting headers. Without Varnish, a plugin such as WP Rocket gives good defaults for mostly-static sites. On an Nginx (non-Varnish) setup, pair WP Rocket with the Rocket-Nginx configuration so Nginx serves WP Rocket's cached pages directly, bypassing PHP.

  • Consider a few extra speed plugins - on large sites, Index WP Users for Speed keeps the admin fast when there are many users, and Speed Up Menu reduces the query overhead of large navigation menus.

  • Index the database - a plugin such as Index WP MySQL for Speed adds indexes to the core tables (wp_options, wp_postmeta, wp_posts, and others), which speeds up slow queries on large sites. Install it, add the indexes, then you can remove the plugin again - the indexes stay:

    wp plugin install index-wp-mysql-for-speed
    wp plugin activate index-wp-mysql-for-speed
    wp index-mysql enable wp_commentmeta wp_comments wp_options wp_postmeta wp_posts wp_termmeta wp_usermeta wp_users
    wp plugin deactivate index-wp-mysql-for-speed
    wp plugin uninstall index-wp-mysql-for-speed

    If it reports that the tables are not found, use your site's actual table prefix instead of wp_ (for example SsW6eC_commentmeta, SsW6eC_options, and so on).

  • OPcache - the PHP runtime ships with OPcache; keep it enabled so compiled PHP is reused across requests.

  • Optimize assets and images - use a modern image plugin (WebP, lazy-load) and minify/combine CSS and JS to cut requests.

  • Offload heavy media to a Content Delivery Network (CDN) - front static assets with an HTTP cache/CDN to reduce origin load and improve global latency.

  • Speed up Elementor - if you build pages with Elementor, enable Element Caching under Elementor > Settings > Features. Elementor assembles pages from many queries and template parts on every request; caching those lookups cuts database load and speeds up dynamic and logged-in views.

  • Raise memory in both places - if you increase WordPress memory, raise the PHP limit too, or the change has no effect: set WP_MEMORY_LIMIT / WP_MAX_MEMORY_LIMIT in wp-config.php and memory_limit in .user.ini.

Set a default TTL for the Redis object cache

By default the Redis object cache does not expire keys that were stored without an explicit time-to-live (TTL), so the cache can keep growing until it evicts entries or fills memory. The object-cache drop-in reads a fallback TTL from a constant in object-cache.php (in wp-content):

if ( ! defined( 'WP_REDIS_DEFAULT_EXPIRE_SECONDS' ) ) {
  define( 'WP_REDIS_DEFAULT_EXPIRE_SECONDS', 0 );
}

A value of 0 means "never expire". Set a real fallback - for example 28800 (8 hours) - so keys stored without their own TTL still age out:

if ( ! defined( 'WP_REDIS_DEFAULT_EXPIRE_SECONDS' ) ) {
  define( 'WP_REDIS_DEFAULT_EXPIRE_SECONDS', 28800 );
}

The drop-in talks to the TurboStack Redis cache instance - the socket configured above, or localhost port 6379 with no password. After editing the drop-in, flush the object cache (wp cache flush) so old entries pick up the new default.

This fallback only applies to keys stored without their own lifetime. Code that calls wp_cache_set() with an explicit lifetime keeps that lifetime, and some plugins deliberately store keys that never expire (for example posts keys). To find where such keys are created, search the codebase for the calls and filter for the group you are chasing:

grep -Ri "wp_cache_set(" wp-content/plugins | grep 'posts'

Then give each of those calls a sensible lifetime instead of leaving it unbounded.

Run a real cron job

WordPress's built-in wp-cron.php runs on page loads, so scheduled tasks fire late on quiet sites and too often on busy ones, wasting PHP processes. Replace it with a system cron job.

  1. Disable the web trigger in wp-config.php:
    define( 'DISABLE_WP_CRON', true );
  2. Add a cron entry (every 5 minutes) that runs the due events. Wrap the command in cronlock so a slow run cannot overlap with the next one and pile up PHP processes:

    */5 * * * * cronlock wp cron event run --due-now --path=/var/www/prod/public_html > /dev/null 2>&1
  3. Test it once by hand - it should report the events it executed:
    wp cron event run --due-now --path=/var/www/prod/public_html

For a multisite network, run the events for every site, not just once:

wp site list --field=url --path=/var/www/prod/public_html \
  | xargs -n1 -I{} wp cron event run --due-now --path=/var/www/prod/public_html --url="{}"

Plugin hygiene

  • Do not bulk-update every plugin at once. Blind "update all" is a common cause of broken sites. Update selectively, test, and use Blackfire to find what actually slows the site before changing it.

  • Remove the redundant HTTPS plugin. TurboStack handles HTTPS and redirects at the web-server layer, so the really-simple-ssl plugin is not needed:

    wp plugin deactivate really-simple-ssl
    wp plugin uninstall really-simple-ssl

Sizing and scaling

Defaults are auto-tuned to the host, so do not pre-emptively raise them. Override sizing variables only with measured evidence (slow queries, cache evictions, swap):

Variable Tune when
mysql_innodb_size MySQL working set no longer fits in the buffer pool
redis_memory The object cache evicts keys under normal load
varnish_cache_size Hot pages are being pushed out of the page cache

See Performance tuning for how to measure before you change anything.

Stability

  • Back up before every change. Confirm backups are running so you can restore the database and uploads after a bad plugin or update.

  • Watch Health for CPU, memory, PHP-FPM and database pressure, and act on sustained trends.

  • Keep WordPress, plugins, themes and PHP current - most outages and exploits trace back to stale code; apply security updates promptly.

  • Test on a staging clone first. Trial core upgrades, major plugin changes and theme switches on a copy before publishing to production.