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:
- Disable traffic-triggered cron.
- Run cron via the system.
- Use a queue for heavy jobs.
Disable WP-Cron and use system cron
In wp-config.php:
define('DISABLE_WP_CRON', true);
Then add a crontab entry on the server:
* * * * * php /var/www/site/wp-cron.php >/dev/null 2>&1
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]);
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]);
});
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";
}
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]);
}
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;
}
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);
}
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);
});
}
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:
- Move to system cron + queued jobs.
- Replace “purge all” triggers with URL-level purges.
- 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)