Locked down a Hetzner CX22 hosting 12 WordPress sites for a small agency
One server, twelve client sites, one shared user. We isolated everything.
- Customer
- Boutique web agency
- Team size
- 4 people
- Provider
- Hetzner
- OS
- Debian 12
- Plan · Duration
- Pro · 32 hours
A four-person agency hosting twelve WordPress sites for clients on a single Hetzner CX22. Their concern: a compromise on one client app could pivot to all the others because everything ran as the same www-data user. We hardened the host and added per-vhost isolation.
They'd had a near-miss the previous quarter where a vulnerable plugin on one client's site had let an attacker write to <code>/tmp</code>. Nothing escalated, but it spooked them. They wanted defense-in-depth before a bigger client onboarded.
What we found on the box.
First pass before changing anything. Severity ranges from critical (act immediately) to low (worth knowing).
-
critical
All 12 vhosts running as the same
www-datauser — no inter-app isolation -
high
xmlrpc.phpexposed publicly on every WordPress install -
high
No fail2ban; logs showed ~11k WordPress login attempts / day
-
high
Two installs still running default WordPress salts (never rotated)
-
high
Let's Encrypt auto-renewal silently broken — certs expiring in 9 days
-
medium
14 days of unapplied security patches
-
medium
PHP 8.1 across the box (security support ending soon)
-
medium
No off-box backups — only Hetzner snapshots, untested
What we changed, in order.
Each change is reversible and documented in the handover doc. Commands shown are illustrative.
-
1
One PHP-FPM pool per vhost, each running as its own dedicated system user with chroot.
-
2
NGINX: blocked
xmlrpc.phpat the server level (return 444), rate-limitedwp-login.phpper IP. -
3
fail2ban with
nginx-http-auth,nginx-botsearch, plus a custom WordPress login filter watching the access log. -
4
Migrated PHP 8.1 → 8.3 with a per-site smoke test before cutover.
-
5
Rotated the WordPress auth keys and salts on the two affected installs.
-
6
Fixed the certbot cron path (it was running but couldn't write to
/var/log/letsencrypt). -
7
Layered Hetzner Cloud Firewall on top of UFW + fail2ban as defense-in-depth.
-
8
Set up
resticdaily backups to a Backblaze B2 bucket; retention 7 daily / 4 weekly. Restore-tested before handover. -
9
Wrote a per-vhost rollback runbook the agency could follow during client incidents.
The numbers that moved.
Representative figures from this engagement. Real, named-customer studies will publish actual numbers with a link to verify.
Other sample engagements
See allHand us your VPS, get an engagement like this one.
Pick a plan, send the credentials through the encrypted form, and we'll come back with the same kind of audit + hardening + handover the studies above describe.