Drupal best practices

Performance and stability best practices for Drupal on TurboStack, covering the Nginx vhost, Varnish full-page cache, Redis, OPcache, cron, and sizing.

This page collects the settings and habits that keep a Drupal site fast and stable on TurboStack. Drupal is a configuration-only application on the platform: you bring your own codebase and database and deploy with Git deployment, while TurboStack provisions and tunes the surrounding stack.

What TurboStack configures for you

When you set an app's type to drupal, the platform provisions and pre-tunes several pieces so you do not have to:

  • Nginx vhost tuned for Drupal. A Drupal-aware server config is written to your app's nginx/50main.conf. It sets the document root to public_html, routes clean URLs through index.php, passes image-style and asset generation back to Drupal, and serves static files with long expires headers.
  • PHP-FPM backend. Requests for .php (and update.php) are passed to a dedicated PHP-FPM pool for your user/app, with a generous fastcgi_read_timeout for long operations like updates and imports.
  • Security hardening in the vhost. Direct access is denied to PHP in sites/*/files, scripts under vendor/, and private file paths. Dotfiles (except .well-known) and source files such as .module, .inc, .twig, .yml, composer.json, and *.sql are also blocked.
  • Varnish full-page cache (optional). When you enable Varnish, a Drupal-specific VCL is installed. It is built for the Advanced Varnish module: cache-tag and wildcard BAN invalidation, per-role cache bins via the ADVBIN cookie, ESI/BigPipe support, gzip compression, and cookie stripping for static assets. Authenticated, /user, /admin, and cron/install/update paths bypass the cache.
  • Redis service. Redis can be enabled as a service for object and session caching (you must point Drupal at it - see below).

Combine Drupal's own production guidance with the platform options. Enable services on the host's Services tab.

  • Use Redis for cache and sessions. Enable the Redis service, install the Drupal redis contrib module, and configure the cache backends in settings.php. This removes cache and session load from MySQL. See Redis object and session cache for the config block and TTL tuning.
  • Enable Varnish for anonymous traffic. Turn on Varnish and install the Advanced Varnish module so cache-tag invalidation works with the supplied VCL. Anonymous page views are then served without hitting PHP.
  • Turn on Drupal's own caches. In production, enable page and dynamic page caching and the internal CSS/JS aggregation; never run with the Twig debug cache disabled.
  • Aggregate and compress assets. Enable Cascading Style Sheets (CSS) and JavaScript (JS) aggregation; the Nginx config already serves the compiled files with far-future expiry.
  • Keep OPcache warm. PHP OPcache caches compiled code; keep it enabled and sized for your module count to cut per-request overhead.
  • Optimize images. Use responsive image styles and modern formats (WebP/AVIF), which the vhost serves with caching.
  • Run cron via Drush, not the web cron. Disable Drupal's automated web cron and schedule drush cron so cron is decoupled from page requests. Use a Scheduled task / cron service on the host.
  • Use a Content Delivery Network (CDN) in front of the site for static assets and global reach where appropriate.

Redis object and session cache

Enable the Redis service, install the Drupal redis contrib module, then point Drupal at the cache instance over its socket. TurboStack runs the cache instance on port 6379 (socket /var/run/redis/redis.sock) with no password. Add this to settings.php:

$settings['redis.connection']['interface'] = 'PhpRedis';
$settings['redis.connection']['host'] = '/var/run/redis/redis.sock';
$settings['redis.connection']['port'] = 6379;
$settings['redis.connection']['password'] = '';
$settings['redis.connection']['prefix'] = 'example:';

Use a unique prefix per site so several sites on one host do not overwrite each other's keys. Clear all caches after the change (drush cr).

Tune the cache Time to Live (TTL)

The Drupal Redis module gives permanent cache items a long default lifetime. The constant LIFETIME_PERM_DEFAULT in modules/contrib/redis/src/Cache/CacheBase.php is 31536000 (one year): any key without an explicit TTL falls back to that. A one-year fallback lets stale bins build up and fill Redis.

You do not need to edit the module. Set a shorter TTL per cache bin in settings.php with the perm_ttl_<bin> setting:

$settings['redis.settings']['perm_ttl_cache_menu'] = 21600; // 6 hours

This gives the menu bin a six-hour lifetime while other bins keep the default. Add one line per bin you want to cap. To decide which bins matter, inspect the keys (for example with Redis Insight) and sort by TTL, then purge the old keys:

drush cr
tscli redis clear

Sizing and scaling

TurboStack auto-tunes service sizes to the host. Change them only when you have measured evidence (slow queries, cache evictions, memory pressure):

Variable Tune when
mysql_innodb_size Working set larger than the buffer pool; frequent disk reads
redis_memory Cache evictions / low hit rate in Redis
varnish_cache_size Anonymous hit rate dropping as content grows

See Performance tuning for how to measure before changing defaults, and increase PHP memory_limit only for genuinely heavy operations (config import, large migrations).

Stability

  • Back up before changes. Confirm scheduled Backups cover both the database and sites/default/files, and take one before deploys or drush updb/config imports.
  • Watch Health. Monitor CPU, memory, and service status, especially after a deploy.
  • Keep versions current. Track security releases for Drupal core, contrib modules, and the PHP runtime.
  • Manage config in Git. Export with drush cex and import with drush cim so configuration moves predictably between environments.
  • Test on a staging clone before applying updates or module changes to production.