Home Server & Self-Hosting

etckeeper on a Debian Home Server - Give /etc a Readable History

etckeeper on a Debian Home Server - Give /etc a Readable History

A Debian server can keep running after a configuration change and still leave an awkward question for later: what exactly changed? Perhaps an upgrade touched a file under /etc, an administrator adjusted an Nginx site, or a working setting was replaced during troubleshooting. A backup may recover files, but it does not automatically provide a readable explanation of each small change.

etckeeper addresses that narrower problem by placing /etc under a version-control system, normally Git. It can make configuration history inspectable and can integrate with package operations. That sounds simple, but /etc is not an ordinary source-code directory: it contains secrets, live service settings, ownership information, and files whose wrong restoration can stop a machine from working. The useful question is therefore not merely how to install etckeeper, but how to use its history without trusting it to do jobs it does not do.

What etckeeper records

Git is good at recording file contents and showing differences between snapshots. It does not normally preserve every owner, permission, or empty-directory detail that matters under /etc. The upstream etckeeper README explains that etckeeper supplements the repository with metadata in /etc/.etckeeper, updated by a pre-commit hook. It also warns about special files that a normal version-control system may not represent.

The result is best understood as a configuration journal. It can answer questions such as these:

  • Which tracked files differ from the last commit?
  • Which lines changed in a service configuration?
  • Did a package operation add, remove, or edit files under /etc?
  • What did one tracked file contain at an earlier revision?

It cannot tell whether an Nginx directive is valid, whether a firewall rule matches the intended policy, or whether a historical configuration is still compatible with the packages installed today. Recording a change and understanding a change are separate tasks.

Install, then inspect instead of assuming

On Debian, installation begins with the distribution package:

sudo apt update
sudo apt install etckeeper

The upstream documentation says a packaged installation will probably initialize the repository automatically. “Probably” matters: packaging and local state can differ. Check rather than assume.

sudo test -d /etc/.git && echo "Git repository present"
sudo git -C /etc status

If no repository exists, the Debian etckeeper(8) manual documents init as the command that initializes the configured version-control system:

sudo etckeeper init
sudo git -C /etc status

Do not hurry past that first status report. Review what is tracked, what is ignored, and whether machine-specific generated files would create constant noise. The official Git status documentation distinguishes changes in the working tree, changes staged in the index, and untracked paths. That distinction is especially useful here because the working tree is the live configuration, not a disposable checkout.

etckeeper manages part of the ignore file, while allowing local rules outside its managed block. Ignoring a file is not merely a tidiness decision. If a file is excluded, its future changes will not appear in this journal. If it has already been committed, adding an ignore rule does not erase the old content from Git history.

A deliberate maintenance loop

A small workflow makes the history more useful than a pile of unexplained snapshots. Before editing, check whether an earlier change is already waiting:

sudo git -C /etc status --short
sudo git -C /etc diff

status names affected paths; diff shows line-level unstaged changes. The Pro Git explanation of recording changes is useful here: a commit is a snapshot of prepared state, while unstaged and untracked content require attention. A clean starting point makes it easier to connect the next diff to the maintenance task at hand.

After changing one service, use that service's own validation mechanism before reload or restart. Nginx, OpenSSH, sudoers, and other components do not share one universal validator, so a copied command cannot replace their current documentation. etckeeper does not perform this semantic check for them.

Then inspect the resulting diff and record a message that explains intent:

sudo git -C /etc diff
sudo etckeeper commit "Restrict example service to the LAN"
sudo git -C /etc status --short

The etckeeper commit command records all changes under its management. That is convenient, but it also means the diff should be read first. An unrelated password change, package-generated edit, or temporary file should not be silently bundled into a misleading commit message.

Small, purposeful commits create a timeline that can be read later. “Restrict example service to the LAN” carries more information than “update config.” The message still does not prove the change was correct; it preserves the operator's stated reason so the diff has context.

What happens around APT

etckeeper's package-manager integration is one reason to use it instead of a bare Git repository. According to the upstream README and Debian manual, its pre-install hook checks the state before package work, while its post-install hook adds interesting files and commits changes made by packages. This can separate package-driven edits from later manual work.

Automation does not eliminate review. A package commit says that files changed around an installation or upgrade; it does not establish that every difference came from one package, nor that the new configuration has the desired behavior. Scheduling of additional periodic commits can also depend on the installed package and local configuration. For a dependable mental model, rely on observed repository history and explicit manual commits, not an assumed timer.

The history can be browsed with ordinary Git commands:

sudo git -C /etc log --oneline --decorate -n 10
sudo git -C /etc show --stat HEAD
sudo git -C /etc show HEAD -- nginx/

The final command narrows the displayed commit to a path relative to /etc. Replace nginx/ with the relevant tracked path. Reading a focused diff is usually safer and more informative than treating a whole package snapshot as one indivisible event.

The repository is sensitive data

This is the most important limit. /etc can contain password hashes, private keys, database credentials, tokens, Wi-Fi secrets, and access-control material. The upstream security warning uses /etc/shadow as the obvious example and says the entire .git directory must remain secret. Git stores historical objects; restricting the current working file is not enough if repository objects can be read.

etckeeper creates its repository with restrictive access, but that protection can be lost when someone copies, clones, archives, or pushes it elsewhere. A convenient public or broadly shared Git host is therefore not an appropriate default remote. Any remote copy needs an explicit threat model, trusted storage, secure transport, restricted access, retention decisions, and a tested recovery path.

There is a second consequence: deleting a secret from the current file does not remove earlier copies from history. If a secret was exposed to an unintended reader, rewriting Git history is not a substitute for rotating that secret. History cleanup may reduce retained exposure, but the credential must be treated according to where it could already have been copied.

History is not a backup

A repository stored at /etc/.git is on the same system, and commonly the same disk, as the files it records. Disk failure, filesystem corruption, theft, or a sufficiently privileged compromise can remove or alter both. Local history is valuable for inspection and small recovery tasks, but it is not an independent backup by itself.

Conversely, a backup and etckeeper answer different questions. A backup should recover data after loss. A readable commit history should explain incremental changes. An operator may reasonably use both, but the sensitive repository needs at least the same care as the original secrets. “Push it somewhere” is not a complete backup design.

Recover one thing at a time

The tempting recovery command is often the most dangerous one: check out an old revision over live /etc. The upstream tutorial warns that Git is operating directly on the system configuration and recommends doing potentially dangerous work in a separate staging directory. A broad checkout may replace unrelated files, revive obsolete settings, or combine old configuration with current package versions.

A narrower approach starts by locating the relevant revision and inspecting one file without changing the working tree:

sudo git -C /etc log --oneline -- path/to/file
sudo git -C /etc diff <older-revision>..HEAD -- path/to/file
sudo git -C /etc show <older-revision>:path/to/file

The official Git show documentation describes how the command displays objects, including plain blob contents. Redirecting that output to a root-only staging file can be useful, but first confirm the revision and path. Then compare the historical content with the live file, adapt it for current software if necessary, run the service-specific validator, install the intended change, and record a new commit.

This is slower than a blind checkout, and that is a feature. Recovery under /etc should preserve the distinction between “this file used to contain these bytes” and “these bytes are safe for the server now.”

When etckeeper is too much, or not enough

For a nearly immutable appliance with reproducible images and configuration generated entirely elsewhere, a live Git repository may duplicate a stronger source-of-truth workflow. For a machine with frequent generated churn, the journal can become noisy unless ignore rules are chosen carefully.

At the other extreme, etckeeper is not configuration management. It does not declare the desired state of many machines, distribute changes, protect secrets by design, or prove that a restored server can boot. It also does not replace service tests, monitoring, backups, or restore drills. Its strength is smaller: it gives an ordinary server a legible memory of configuration changes.

Conclusion

etckeeper can turn “something changed under /etc” into a question that has evidence: a status report, a diff, a commit message, and an earlier file to inspect. That is useful on a home server precisely because many small manual and package-driven changes accumulate over time.

The same history deserves caution. It contains sensitive material, lives beside active configuration, and cannot decide whether an old setting is valid today. Treat the repository as secret, keep independent backups, validate with the relevant service, and recover narrowly. Used within those boundaries, etckeeper is not an undo button; it is a clearer record of how the machine arrived at its current state.

References