Track Linux Config Drift with etckeeper + systemd.path: Immediate /etc History Without Guesswork
Linux configuration drift is rarely dramatic. It is usually one line in sshd_config, a package upgrade that rewrites a default, or a hurried edit at 2 AM that nobody remembers the next morning.
If you already use Git for code, the obvious question is: why not keep /etc under version control too?
That is exactly what etckeeper is for. It puts /etc in a Git repository, tracks metadata that Git normally misses, and can automatically commit changes made during package operations. With one extra systemd.path unit, you can also record manual edits to /etc much closer to real time.
This guide shows a practical setup that is:
- easy to audit
- safe enough for real Linux systems
- focused on complete, working examples
- explicit about the security trade-offs
Why this is different from file integrity monitoring
I already covered full file integrity monitoring with AIDE. That solves a different problem: detecting whether protected files changed at all.
This setup is about operational history for /etc:
- what changed
- when it changed
- which package operation triggered it
- what the previous content looked like
- how to roll back a bad edit quickly
In other words: this is configuration history, not tamper detection.
What etckeeper actually does
According to the upstream README, etckeeper stores /etc in a VCS repository, hooks into package managers such as apt to automatically commit configuration changes made during package upgrades, and stores metadata Git does not normally track, including permissions that matter for files like /etc/shadow.
That last part matters. Plain Git is not enough for /etc because permissions and ownership are part of the configuration story.
The security warning you should not skip
Before we touch commands, the biggest caveat:
/etc can contain sensitive material.
The upstream etckeeper documentation explicitly warns that versioning /etc creates another copy of secret files such as /etc/shadow, and that the repository contents must remain private. If you push it anywhere, that remote must be trusted and locked down.
So the rule is simple:
- keep
/etc/.gitreadable by root only - do not push to a public remote
- treat any backup of the repository as sensitive data
If your system stores private keys or secrets under /etc, assume the Git history is sensitive too.
Step 1: Install etckeeper
Debian / Ubuntu
sudo apt update
sudo apt install -y etckeeper git
Fedora
sudo dnf install -y etckeeper git
Arch Linux
sudo pacman -S etckeeper git
On many distributions, package installation initializes the integration hooks automatically, but I still recommend verifying the config and making the first commit yourself.
Step 2: Check the main config
Open:
sudo editor /etc/etckeeper/etckeeper.conf
Things to verify:
VCS="git"- your package-manager settings are correct for the distro
- automatic commits are enabled the way you want
For a typical Git-based setup, the defaults are usually fine.
Step 3: Initialize /etc and create the first commit
sudo etckeeper init
sudo git -C /etc status
sudo git -C /etc commit -m "Initial /etc check-in"
Why use git -C /etc here instead of cd /etc? It is easier to script and makes it obvious which repository is being modified.
Then pack the repository to save space:
sudo git -C /etc gc
The upstream README recommends git gc after the initial commit because it can reduce the repository footprint significantly.
Step 4: Verify package-manager auto-commits
One of the most useful etckeeper features is automatic history around package changes.
The upstream README documents that before package installation, etckeeper checks whether /etc has uncommitted changes, and after package installation it adds interesting files and commits the result.
A quick verification workflow on Debian or Ubuntu:
sudo apt install -y tree
sudo git -C /etc log --oneline -n 5
You should see commit messages created around the package operation.
On Arch Linux, the ArchWiki documents that package hooks automatically run before and after package installation, update, and removal in current etckeeper packaging.
Step 5: Add near-real-time commits for manual /etc edits
Package hooks are great, but they do not capture every hand edit immediately.
This is where systemd.path helps.
From the systemd.path(5) manual:
-
PathChanged=activates a service when a watched file or directory changes and the open file gets closed - path units use
inotify(7)under the hood - they are not appropriate for remote NFS changes because they inherit inotify limitations
That makes PathChanged=/etc a practical way to notice local edits and trigger a small commit service.
Create the commit helper script
Create /usr/local/sbin/etckeeper-path-commit.sh:
sudo install -d -m 0755 /usr/local/sbin
sudo tee /usr/local/sbin/etckeeper-path-commit.sh >/dev/null <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
# Avoid empty commits.
if etckeeper vcs status --porcelain | grep -q .; then
etckeeper commit "auto: capture manual /etc changes"
fi
EOF
sudo chmod 0755 /usr/local/sbin/etckeeper-path-commit.sh
Create the service unit
sudo tee /etc/systemd/system/etckeeper-path-commit.service >/dev/null <<'EOF'
[Unit]
Description=Commit manual /etc changes with etckeeper
Documentation=https://etckeeper.branchable.com/README/
[Service]
Type=oneshot
ExecStart=/usr/local/sbin/etckeeper-path-commit.sh
Nice=10
IOSchedulingClass=idle
EOF
Create the path unit
sudo tee /etc/systemd/system/etckeeper-path-commit.path >/dev/null <<'EOF'
[Unit]
Description=Watch /etc for manual configuration changes
Documentation=man:systemd.path(5)
[Path]
PathChanged=/etc
Unit=etckeeper-path-commit.service
[Install]
WantedBy=multi-user.target
EOF
Enable it
sudo systemctl daemon-reload
sudo systemctl enable --now etckeeper-path-commit.path
Test it
echo '# drift test' | sudo tee -a /etc/motd >/dev/null
sleep 2
sudo git -C /etc log --oneline -n 3
sudo git -C /etc diff HEAD~1 HEAD -- motd
If the path unit triggered correctly, you should see a fresh commit containing the motd change.
Why I use PathChanged= instead of PathModified=
The systemd.path(5) manual distinguishes the two:
-
PathChanged=triggers when the watched file is changed and then closed -
PathModified=also triggers on simple writes
For /etc, PathChanged= is usually the calmer choice because it reduces noisy mid-write activations. You want a commit after an edit is finished, not during every write burst from an editor.
Step 6: Add a private backup remote
A local /etc/.git history is already useful, but a second private copy makes recovery much easier.
The upstream etckeeper README gives a safe pattern: create a root-only bare repository on a trusted host, then push over SSH.
On the backup host:
ssh backup-host 'mkdir -p /srv/etckeeper/server1 && chmod 700 /srv/etckeeper/server1 && git init --bare /srv/etckeeper/server1'
On the machine you are protecting:
sudo git -C /etc remote add backup ssh://backup-host/srv/etckeeper/server1
sudo git -C /etc push backup --all
If you want to push after every commit, etckeeper supports a PUSH_REMOTE setting or custom commit hooks, but I would start with manual pushes until you are sure the security model is right.
Useful day-2 commands
See what changed recently
sudo git -C /etc log --stat --oneline -n 10
See exactly what changed in one file
sudo git -C /etc log -p -- ssh/sshd_config
Compare current config with yesterday's state
sudo git -C /etc diff HEAD~1 HEAD
Roll back one file carefully
sudo git -C /etc restore --source=HEAD~1 -- ssh/sshd_config
sudo etckeeper commit "restore sshd_config from previous revision"
Do not blindly roll back all of /etc on a live box unless you fully understand the consequences. Restoring a single file is usually safer.
Operational caveats
A few things are worth saying plainly:
1) /etc is not a normal application repo
The etckeeper README warns that checking out older branches or revisions directly into /etc can be risky, because Git operates on the live system. If you do a branch checkout or restore older states broadly, rerun etckeeper init so metadata handling is refreshed.
2) Path units are not magic
systemd.path uses inotify. That means:
- it is good for local filesystem changes
- it is not reliable for remote NFS-originated changes
- very noisy writers can trigger more often than you expect
If you have software rewriting /etc constantly, add filtering or skip the path unit and rely on daily/package-manager commits.
3) Secrets still need separate handling
This article is about change history, not secret management. If you are trying to remove secrets from .env files or service definitions, use a dedicated mechanism such as systemd credentials or an external secret manager.
A sane minimal setup
If you want the shortest path to value, do just this:
- install
etckeeper - initialize
/etc - make the first commit
- verify package-manager auto-commits
- optionally enable the path unit if manual edits are common
- push to a private bare Git remote over SSH
That gets you a useful audit trail with very little operational overhead.
Final thought
When Linux configuration breaks, the hardest part is often not fixing it. It is reconstructing the story.
etckeeper gives /etc a memory. Adding systemd.path makes that memory more immediate.
And when the next "nothing changed" incident lands on your desk, having a real commit history beats guesswork every time.
References
- etckeeper upstream README: https://etckeeper.branchable.com/README/
- etckeeper website: https://etckeeper.branchable.com/
- ArchWiki: Etckeeper: https://wiki.archlinux.org/title/Etckeeper
-
systemd.path(5)manual: https://man7.org/linux/man-pages/man5/systemd.path.5.html -
systemd.timer(5)manual: https://man7.org/linux/man-pages/man5/systemd.timer.5.html -
git-init(1)manual: https://man7.org/linux/man-pages/man1/git-init.1.html
Top comments (0)