DEV Community

Cover image for WordPress on Hetzner: Plugin Architecture for Speed
SignalFast
SignalFast

Posted on • Originally published at cloudstrap.dev

WordPress on Hetzner: Plugin Architecture for Speed

TL;DR

If you run WordPress on Hetzner, you’ll get the best results when your plugins acknowledge the infrastructure: CPU spikes during cache warmups, I/O variance on volumes, cron reliability, and edge caching realities. This post breaks down architecture choices for infrastructure-aware plugins (like the ones at CloudStrap) and includes implementation patterns: feature flags, safe cache purges, WP-CLI integration, and “no-surprises” defaults.

WordPress on Hetzner: Plugin Architecture for Speed

Hetzner makes it tempting to build a lean stack: a modest cloud instance, Nginx, PHP-FPM, MariaDB, and you’re done. But WordPress on Hetzner behaves differently than “whatever shared hosting is doing behind the curtain.” You control the box, which means you also own the failure modes: noisy neighbors on network storage, PHP worker saturation, cron drift, and disk I/O shaping.

The punchline: your plugin layer becomes part of your infrastructure. If plugins assume infinite CPU, always-on cron, or “cache purge means delete everything now,” they’ll fight your server.

This is the mental model I use when designing plugins specifically for WordPress on Hetzner:

  • Prefer predictable work over “best effort” bursts.
  • Treat cache invalidation as a workflow, not a button.
  • Assume you’ll run multiple sites per server (agencies do).
  • Make observability a first-class feature (logs, metrics hooks).

The problem: generic plugins ignore infrastructure constraints

On paper, WordPress plugins are “just PHP.” In practice, they schedule work, do I/O, hit external APIs, and mutate caches. Infrastructure-blind plugins usually fail in a few ways:

  • Cron assumptions: WP-Cron is traffic-triggered, so low-traffic sites miss jobs. High-traffic sites can stampede.
  • Cache purges that spike CPU/I/O: deleting large caches or regenerating thumbnails can saturate your instance.
  • Storage mismatches: media on object storage or mounted volumes changes latency characteristics.
  • Deploy friction: config stored only in the DB makes it harder to standardize across environments.

If you’re self-hosting WordPress on Hetzner, you want tooling that behaves like a good citizen on a VPS.

The solution: infrastructure-aware plugin design

The goal isn’t “more features.” It’s fewer surprises. For WordPress on Hetzner, that usually means:

  • Idempotent operations (safe to retry)
  • Rate-limited background work
  • Small, composable modules instead of one mega-plugin
  • Config that can be code-driven (constants/env) but still manageable in wp-admin

A practical north star: treat every expensive action as a job with a queue, even if the queue is just the options table plus Action Scheduler.

Design pattern: a thin core + adapters

A pattern that scales is a “thin core” plugin with adapters for server specifics:

  • Core: settings, admin UI, WP-CLI commands, job orchestration
  • Adapters: Nginx fastcgi cache purge, Redis object cache flush, Hetzner Storage Box backup triggers, etc.

That keeps the plugin portable, while still optimizing for WordPress on Hetzner deployments.

Implementation: safe background work (cron + queue)

If you rely on WP-Cron alone, you’ll eventually get a support ticket that reads: “Backups stopped” or “Cache never warms.” For WordPress on Hetzner, I prefer this setup:

  1. Disable traffic-triggered cron.
  2. Run cron via the system.
  3. Use a queue for heavy jobs.

Disable WP-Cron and use system cron

In wp-config.php:

define('DISABLE_WP_CRON', true);
Enter fullscreen mode Exit fullscreen mode

Then add a crontab entry on the server:

* * * * * php /var/www/site/wp-cron.php >/dev/null 2>&1
Enter fullscreen mode Exit fullscreen mode

This makes job execution deterministic—especially important for WordPress on Hetzner sites with variable traffic patterns.

Use Action Scheduler for jobs

WooCommerce’s Action Scheduler is widely deployed and battle-tested. It’s a solid default for background tasks.

Example: enqueue a cache warm job in small batches:

as_enqueue_async_action('cloudstrap_warm_cache', ['post_id' => $post_id]);
Enter fullscreen mode Exit fullscreen mode

Then process with strict limits:

add_action('cloudstrap_warm_cache', function($args) {
  $post_id = (int) ($args['post_id'] ?? 0);
  if (!$post_id) return;

  // Warm a single URL (cheap, repeatable)
  $url = get_permalink($post_id);
  wp_remote_get($url, ['timeout' => 5, 'redirection' => 0]);
});
Enter fullscreen mode Exit fullscreen mode

Batching like this avoids the “purge + rebuild everything now” spike that can wreck WordPress on Hetzner when you’re on a smaller instance.

Implementation: cache purging that won’t melt your box

Cache purging is where many stacks get fragile. A good approach for WordPress on Hetzner is:

  • Purge surgically (single URL) on content updates.
  • Purge tags/groups when taxonomy/global templates change.
  • Reserve “purge all” for explicit manual operations.

Nginx fastcgi cache purge via a local endpoint

If you run Nginx fastcgi_cache, you can expose a protected purge endpoint accessible only from localhost.

Nginx snippet (conceptual):

location ~ /purge(/.*) {
  allow 127.0.0.1;
  deny all;
  fastcgi_cache_purge WORDPRESS "$scheme$request_method$host$1";
}
Enter fullscreen mode Exit fullscreen mode

Then the plugin calls it:

function cloudstrap_purge_url($path) {
  $purge = 'http://127.0.0.1/purge' . $path;
  return wp_remote_request($purge, ['method' => 'GET', 'timeout' => 2]);
}
Enter fullscreen mode Exit fullscreen mode

This keeps purge traffic on-box, fast, and safe—exactly what you want when operating WordPress on Hetzner.

Implementation: configuration as code (without losing UX)

A recurring pain point when managing multiple WordPress on Hetzner sites is drift: different caching toggles, different TTLs, different exclusions.

A balanced approach:

  • Allow constants/env overrides for “fleet defaults.”
  • Keep UI for per-site tweaks.

Example:

function cloudstrap_get_setting($key, $default = null) {
  $const = 'CLOUDSTRAP_' . strtoupper($key);
  if (defined($const)) return constant($const);

  $opts = get_option('cloudstrap_settings', []);
  return $opts[$key] ?? $default;
}
Enter fullscreen mode Exit fullscreen mode

This makes staging/prod parity easier while still being friendly to site admins.

Observability: logs you can actually use

When you self-host WordPress on Hetzner, you don’t have a managed host’s dashboards by default. Your plugins should help.

Practical logging guidelines:

  • Log events, not noise (purge requested, job enqueued, job failed).
  • Include correlation IDs for batch operations.
  • Emit timings for expensive steps.

Minimal example with error_log (fine for starters):

function cloudstrap_log($message, array $context = []) {
  $line = '[cloudstrap] ' . $message . ' ' . wp_json_encode($context);
  error_log($line);
}
Enter fullscreen mode Exit fullscreen mode

If you later ship logs to Loki/ELK, those JSON contexts become gold.

Gotchas from real Hetzner deployments

These are the “I wish someone told me” items that show up after you’ve run WordPress on Hetzner for a while:

CPU spikes from cache warmers

A naive warmer that crawls the whole sitemap will max out PHP-FPM workers. Fix it by:

  • warming a small set of URLs per minute
  • using a dedicated low-priority worker or separate PHP-FPM pool
  • short timeouts and no redirects

Redis object cache flush is not a strategy

Flushing Redis on every deploy punishes logged-in users and increases DB load. Prefer versioned cache keys or group invalidation.

Backups: Storage Box latency changes assumptions

If you push backups to a remote target, latency matters. Stream and chunk rather than building giant archives in /tmp.

Hetzner’s product docs are clear about service boundaries; treat them as part of your design constraints. Start with the official Hetzner Docs to understand the primitives you’re building on.

What I learned designing plugins for WordPress on Hetzner

A few principles that keep paying off:

  • Default to safe: make the “easy button” conservative (rate limits, timeouts, partial purges).
  • Make expensive actions explicit: “Purge all” should feel like pulling a fire alarm.
  • Prefer composability: small plugins/modules are easier to reason about than an all-in-one toolbox.
  • Ship WP-CLI commands for repeatable operations.

Example WP-CLI command scaffold:

if (defined('WP_CLI') && WP_CLI) {
  WP_CLI::add_command('cloudstrap cache-purge', function($args) {
    $path = $args[0] ?? '/';
    $res = cloudstrap_purge_url($path);
    WP_CLI::success('Purged: ' . $path);
  });
}
Enter fullscreen mode Exit fullscreen mode

For agencies standardizing WordPress on Hetzner, WP-CLI is the difference between “click around” and “automate safely.”

A helpful next step (not a migration project)

If you want to improve your WordPress on Hetzner reliability this week, pick one change that reduces uncertainty:

  1. Move to system cron + queued jobs.
  2. Replace “purge all” triggers with URL-level purges.
  3. Add WP-CLI commands for your top 2 operational tasks.

If you’re exploring infrastructure-aware tooling, take a look at what we’re building at CloudStrap – WordPress Tools Built for Hetzner. Even if you don’t use it, the design goal—plugins that respect Hetzner constraints—can guide your own implementations.


Originally about WordPress on Hetzner.

Top comments (0)