LinuxXXXIV · Configuration ManagementDrift detection
Config drift detection - keeping reality aligned with declared state
What you'll learn
- Detect configuration drift
- Schedule drift checks
- Alert on critical drift
- Remediate automatically
Prerequisites
Verified against Ubuntu 24.04 LTS · Debian 12 (Bookworm) · RHEL 9.x · Rocky Linux 9.x · AlmaLinux 9.x · Linux kernel 6.1 LTS / 6.6 LTS · systemd 255+ · OpenSSH 8.7p1 (RHEL 9) / 9.6p1 (Ubuntu 24.04) · nftables 1.0.x · chrony 4.x · Pacemaker 2.1.x · Corosync 3.1.x · 2026-08-09
Configuration drift is the silent killer of infrastructure. Hosts that started identical end up different; nobody knows when or why. Drift detection catches this.
What is drift?
Drift is any change to a host’s configuration that is not recorded in the desired state:
- A user manually edits /etc/ssh/sshd_config.
- A package is installed ad-hoc.
- A service is enabled but should not be.
- A file’s permissions change.
Drift is dangerous because the desired state is no longer true, but the CM system thinks it is.
Detect drift with Ansible
# Check mode: report what would change without making changes
ansible-playbook -i prod site.yml --check --diff
Output:
TASK [Configure nginx]
--- before
+++ after
@@ -1,3 +1,3 @@
worker_processes 1;
-listen 80;
+listen 8080;
changed: [web01]
The diff shows exactly what is drifted.
Schedule drift checks
# /etc/cron.d/ansible-drift-check
0 2 * * * root install -m 0600 -o root -g root /dev/null /var/log/drift-check.log && /usr/bin/ansible-playbook -i /etc/ansible/prod /etc/ansible/site.yml --check --diff >> /var/log/drift-check.log 2>&1
Daily checks. Review the log weekly.
The install -m 0600 step is not decoration. Root cron runs
with umask 022, so a shell redirection creates the log mode
0644 - readable by every local account on the host. The next
section explains why that matters.
Add the file to logrotate with the same mode, or the first rotation undoes the fix:
# /etc/logrotate.d/drift-check
/var/log/drift-check.log {
weekly
rotate 8
create 0600 root root
missingok
}
Alert on drift
Run the check from the Ansible control node, and drive the
alert off the changed count:
#!/usr/bin/env bash
# /usr/local/sbin/drift-check - runs on the control node
set -uo pipefail
out=$(ANSIBLE_STDOUT_CALLBACK=json ansible-playbook \
-i /etc/ansible/prod /etc/ansible/site.yml --check 2>&1)
rc=$?
if [ "$rc" -ne 0 ]; then
echo "drift-check FAILED rc=$rc" # a broken check, not drift
exit 2
fi
changed=$(printf '%s' "$out" | jq '[.stats[].changed] | add')
if [ "${changed:-0}" -gt 0 ]; then
echo "DRIFT: $changed resource(s) differ from desired state"
exit 1 # this is the signal monitoring alerts on
fi
exit 0
Exit 1 means drift, exit 2 means the detector itself is broken. Alert on both - a check that cannot run is not a clean check.
Without the JSON callback, parse the play recap instead:
drifted=$(printf '%s\n' "$out" | awk '/PLAY RECAP/,0' \
| grep -oP 'changed=\K[0-9]+' | awk '{s+=$1} END{print s+0}')
[ "$drifted" -gt 0 ] && logger -t drift "drift on $drifted host-task(s)"
Remediate
For critical drift (e.g. SSH config change), auto-remediate:
# Run the playbook for real, not in check mode
ansible-playbook -i prod site.yml --limit web01
For non-critical drift (e.g. cosmetic), log and review: manual decision.
Track drift over time
Store drift reports in a git repo or SIEM:
day=/var/log/drift-history/$(date +%Y%m%d)
install -d -m 0700 -o root -g root "$day"
install -m 0600 -o root -g root /dev/null "$day/$(date +%H%M).log"
ansible-playbook -i /etc/ansible/prod /etc/ansible/site.yml \
--check --diff >> "$day/$(date +%H%M).log" 2>&1
The permissions carry more weight here than in the nightly
job. A history directory keeps every diff indefinitely, so a
single unredacted secret stays on disk - and in every host
backup taken since - until someone notices. Prefer storing
the drift summary (host, task, resource changed) rather
than the raw diff, and keep the raw diff only where the
secrets have been suppressed with no_log.
Trend analysis: a host with increasing drift over time suggests it is being managed manually, not by CM.
What to check
For each host, check:
- Critical config files (/etc/ssh/sshd_config, /etc/pam.d/*, /etc/sudoers).
- Installed packages (vs the desired set).
- Running services (vs the desired set).
- File permissions on sensitive files.
- User accounts (no ad-hoc additions).
- Sudo rules (no ad-hoc additions).
Knowledge check
Knowledge check · 5 questions
Q1. What Ansible flag runs a playbook in check mode?
Q2. Drift detection should run weekly.
Q3. Which of the following are typical drift check targets? Select all that apply.
Q4. Your nightly drift check runs ansible-playbook --check and alerts when the exit code is non-zero. It has reported clean for six months. What is the most likely explanation?
Q5. Redirecting ansible-playbook --check --diff into /var/log/drift-check.log is safe, because secret files are deployed with mode 0600.
Passing score: 75%. Answers are checked in this browser.