WordPress best practices
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 withexpires max.xmlrpc.phpis 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_enabledis set, a WordPress-aware VCL is installed. It bypasses the cache forwp-admin, logged-in users, the WooCommerce cart/checkout/my-account flows, andwp-cron.php. It caches static files for a day, normalises query strings, and strips tracking parameters (utm_*,fbclid,gclid). It supportsPURGEfrom the Proxy Cache Purge plugin. -
wp-config.php - generated with your database credentials (
localhost, dedicated DB and user), fresh secret-key salts andWP_DEBUGoff 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.
Recommended optimizations
-
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 inwp-config.php, then enable it:$redis_server = array( 'host' => '/var/run/redis/redis.sock', 'port' => null, 'database' => 1 );wp redis enable wp transient delete-allWhen several sites share one Redis server, give each a different
databaseid so they do not overwrite each other's cache. -
Use the Varnish full-page cache - enable
varnish_enabledfor 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-speedIf it reports that the tables are not found, use your site's actual table prefix instead of
wp_(for exampleSsW6eC_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_LIMITinwp-config.phpandmemory_limitin.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.
- Disable the web trigger in
wp-config.php:define( 'DISABLE_WP_CRON', true ); -
Add a cron entry (every 5 minutes) that runs the due events. Wrap the command in
cronlockso 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 - 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-sslplugin 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):
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.