Detecting Configuration Drift on Stateful VPS Hosts

Configuration drift is any divergence between the intended state of a host and its actual state. On a stateful VPS — one running a database, mail queue, or persistent application data — drift is dangerous because it accumulates silently. A hand-edited my.cnf, an extra package, a modified sysctl, or a stale cron entry can invalidate your provisioning assumptions and make a rebuild behave differently from the host it replaces. Detecting drift early is cheaper than reconciling it during an incident.

Classify what can drift

  • Package state — installed versions and held packages (apt list --installed, dpkg --get-selections).
  • File content — conventional config paths, service unit overrides, and drop-ins.
  • Kernel and runtime parameters — sysctl values, ulimits, sysfs tunables that reset on reboot.
  • Stateful data — schema versions, applied migrations, queue depths.

Baseline with a manifest

The cheapest detector is a content-addressed manifest of the files that matter. Hash them on a known-good host and diff on a schedule:

PATHLIST='/etc/mysql /etc/nginx /etc/php /etc/ssh/sshd_config'
find $PATHLIST -type f -print0 | sort -z | \
  xargs -0 sha256sum > /var/lib/baseline/files.sha256

# daily comparison
sha256sum -c /var/lib/baseline/files.sha256 2>&1 | grep -v ': OK'

Capture sysctl and ulimits too, since those live in the kernel and are easy to change interactively without touching disk:

sysctl -a 2>/dev/null | sort > /var/lib/baseline/sysctl.txt
ulimit -a > /var/lib/baseline/ulimit.txt
diff -u /var/lib/baseline/sysctl.txt <(sysctl -a 2>/dev/null | sort)

Turn one-off checks into an alert

A manual diff is only useful if someone runs it. Wrap the check in a systemd timer that exits non-zero on drift so monitoring picks it up. The same timer discipline described for scheduling accuracy applies here: use Persistent=true only if a missed run is safe, and always add RandomizedDelaySec.

[Unit]
Description=Config drift check

[Timer]
OnCalendar=*-*-* 04:00:00
RandomizedDelaySec=10min
AccuracySec=5min

[Install]
WantedBy=timers.target

Reconcile, don’t just detect

Drift classDetectorReconcile action
Packagedpkg selections diffReinstall or pin
Config filessha256 manifestRedeploy from template
sysctl/limitssysctl -a diffRe-apply drop-in
SchemaMigration version tableRun pending migrations

For stateful hosts, never auto-reconcile data. Gate schema and data changes behind a human step; auto-reconcile only declarative, idempotent config. A rebuild-from-scratch policy is the strongest drift control, which is why immutable images are worth the effort — if you are choosing infrastructure for this pattern, compare VPS hosting providers on snapshot and rebuild support.

A minimal drift inventory

# everything installed outside the base image
comm -13 <(sort /var/lib/baseline/packages.txt) \
         <(apt-mark showmanual | sort)

# services enabled but not in the baseline
systemctl list-unit-files --state=enabled --no-pager

Run the checks after every deploy and at least once a day on idle hosts. The goal is not zero drift — small, intentional changes are normal — but zero undetected drift. Combined with verified backups, this keeps a stateful VPS reproducible under pressure.

Leave a Reply