
Config drift happens because every server gets touched individually, by a different person, at a different time, under a different deadline. You SSH into server-07 to bump memory_limit for one slow client site, forget to make the same change on server-03, and six months later nobody remembers which server has which value. Multiply that by twenty PHP-FPM pools, twenty Nginx vhost blocks, and twenty sets of CSF firewall rules, and you get a fleet where no two servers behave quite the same way under load — and where a bug report only reproduces on one box out of twenty.
The fix isn't a single tool — it's a discipline: every server's configuration should be traceable to a source of truth, changes should be applied the same way everywhere, and you should be able to answer "how is server-12 different from server-01?" in seconds, not by SSHing into both and eyeballing files.
Drift is also cumulative. A single manual tweak is harmless on its own — the problem is that fleets acquire dozens of them over a year, and nobody wrote down which server got which fix. By the time you're troubleshooting a memory leak that only shows up on three of twenty boxes, you're not debugging PHP anymore, you're debugging your own history of undocumented one-off changes.
The cheapest first step is tracking every change to /etc in a local git repository with etckeeper, so at minimum you have a commit history per server showing exactly what changed and when. Install it, initialize the repo, and it hooks into apt automatically so package upgrades that touch config files get committed too.
This alone won't keep twenty servers in sync — it just gives each server an honest, queryable history. That history is what makes the next steps possible: you can't compare configs meaningfully across servers if you don't first know what changed on each one.
Before you automate anything, find out how far apart your servers actually are. Pull the same file from two hosts with scp and run it through diff — no special tooling required, and it's the fastest way to catch the one server that's still running an old pm.max_children value or a hand-edited server_names_hash_bucket_size.
Run this across your PHP-FPM pool configs (/etc/php/8.3/fpm/pool.d/www.conf), your CSF rules (/etc/csf/csf.conf), and your SSH daemon config (/etc/ssh/sshd_config) on a handful of servers. If you find more than a couple of unexplained differences, that's your signal to stop editing servers by hand.
Do this diff exercise across your whole fleet once, and treat it as a one-time audit rather than a recurring process — manually diffing twenty servers against each other doesn't scale past the second comparison. What it's for is establishing a "known good" reference file to use as the canonical version everywhere else, which is exactly the input the next two approaches need.
Ansible is the standard for this because it's agentless — it connects over SSH, applies a declared state, and gives you the same result whether you're targeting one server or fifty. Define your fleet in an inventory file, group servers logically, and write a playbook that pushes the same Nginx and PHP-FPM config to every host in the group.
The point of a playbook isn't the YAML — it's that config changes stop being something you remember to do on twenty servers and become something you run once. If a playbook run fails on server-14, you know immediately, instead of finding out three weeks later when a client complains that one site behaves differently from the rest.
All of the above manages drift after it's already possible. The more durable fix is removing the opportunity for drift at the point a server joins your fleet. Connecting a new server to CloudStick means it starts from the same package set as every other server in the fleet — no manual apt install drift between boxes. CloudStick installs its own custom-compiled, namespaced packages under /CloudStick/Packages/ (nginx-cs, apache2-cs, php81cs-fpm through php84cs-fpm), so a server provisioned on Hetzner and a server provisioned on DigitalOcean both get an identical, versioned stack. CSF firewall rules, PHP-FPM pool defaults (pm=ondemand, pm.max_children=50, memory_limit=256M), and SSH hardening are applied the same way at connect time, regardless of the underlying VPS provider or OS image.
In practice, the fleets that stay consistent combine both approaches: a platform that guarantees the same baseline on day one, and a lightweight process — etckeeper for history, a git repo plus rsync or an Ansible playbook for pushing intentional changes — for keeping it that way as the fleet grows. Pick one sync method, apply it consistently, and a twenty-server fleet stops being twenty separate problems.

