How to track /etc changes with etckeeper
etckeeper turns /etc into a git repository. On Debian and Ubuntu, sudo apt install etckeeper creates /etc/.git and the first commit; after that, every apt run and a daily job commit changes on their own, sudo etckeeper commit "message" records your edits, and sudo git -C /etc log shows what changed, when and by whom. It is a change history, not a backup: the repository lives on the same disk as /etc.
Install etckeeper
sudo apt install etckeeperThe package pulls in git, then runs etckeeper init and etckeeper commit "Initial commit" itself. Ubuntu 24.04 and Debian 12 ship etckeeper 1.18.20, Ubuntu 22.04 has 1.18.16 and Debian 13 has 1.18.22. Check that the first commit exists:
sudo git -C /etc log --onelineIf the list is empty (another distribution, or git was missing), set it up by hand. init creates /etc/.git with mode 700, a .gitignore and a pre-commit hook, and stages the files without committing:
sudo etckeeper initsudo etckeeper commit "Initial commit"The .gitignore already skips files that change by themselves, such as ld.so.cache, adjtime, mtab and dpkg's *.dpkg-* copies. Add your own patterns outside its "managed by etckeeper" block, which upgrades rewrite.
What gets committed automatically
| When | Commit message | Run by |
|---|---|---|
| Install | Initial commit | The package's install script |
| Before apt changes packages, if /etc has uncommitted edits | saving uncommitted changes in /etc prior to apt run | etckeeper pre-install, from DPkg::Pre-Invoke in /etc/apt/apt.conf.d/05etckeeper |
| After apt | committing changes in /etc made by "apt install nginx", then the packages that changed | etckeeper post-install, from DPkg::Post-Invoke |
| Once a day, if anything changed | daily autocommit | /etc/cron.daily/etckeeper and etckeeper.timer |
Both daily triggers run /etc/etckeeper/daily, which commits only when /etc has changes, so running both does no harm. The timer fires 15 minutes after boot and then every 24 hours. Upstream calls it opt-in, but Debian's package enables it on install (Debian bug #1149806, reported against 1.18.22). Check yours:
systemctl is-enabled etckeeper.timerAVOID_DAILY_AUTOCOMMITS=1 in /etc/etckeeper/etckeeper.conf stops only the cron job, because the daily script doesn't read that file. To end daily commits, set it and also run sudo systemctl disable --now etckeeper.timer. Teams that want a reason on every change add AVOID_COMMIT_BEFORE_INSTALL=1, which makes apt stop when /etc has uncommitted edits instead of committing them for you.
Commit your own changes
After editing a file, commit with a message that says why:
sudo etckeeper commit "Disable SSH password logins"Run through sudo, etckeeper records the user who ran sudo as the author, with the name and email from their own ~/.gitconfig if they have one, so the log shows who made each change. etckeeper commit stages everything first (git add --all), new and deleted files included; plain sudo git -C /etc commit -a runs the same pre-commit hook but skips new files. In a script, sudo etckeeper unclean succeeds when there are uncommitted changes.
Read the history
These outputs come from a test repository built with etckeeper's commit messages. Paths are relative to /etc. Without sudo, git can't read /etc/.git (mode 700) and stops with fatal: not a git repository (or any of the parent directories): .git.
sudo git -C /etc log --oneline81e4be6 daily autocommit
acab37a committing changes in /etc made by "apt install nginx"
e208226 Initial commitOnly the commits that touched one file:
sudo git -C /etc log --oneline -- ssh/sshd_config81e4be6 daily autocommit
e208226 Initial commitThe newest change to that file, as a diff:
sudo git -C /etc log -p -1 -- ssh/sshd_configcommit 81e4be601b2ed5cd445e6a266288b7f2fe2bcd02
Author: root <root@web1>
Date: Sat Oct 3 06:25:00 2026 +0000
daily autocommit
diff --git a/ssh/sshd_config b/ssh/sshd_config
index afcb082..f742952 100644
--- a/ssh/sshd_config
+++ b/ssh/sshd_config
@@ -1,2 +1,2 @@
PermitRootLogin prohibit-password
-PasswordAuthentication yes
+PasswordAuthentication noSomeone edited sshd_config by hand without committing, so the daily job recorded it as root. Committing your own changes keeps the author and the reason. More commands:
| Command | Shows |
|---|---|
sudo git -C /etc status --short | Files changed since the last commit |
sudo git -C /etc diff | Those changes, line by line |
sudo git -C /etc log --since=2026-10-01 --stat | Commits since a date, with the files each one changed |
sudo git -C /etc blame ssh/sshd_config | The commit, author and date behind each line |
sudo etckeeper vcs log | The same as git log, run in /etc |
Restore a file from history
Look at the old version first:
sudo git -C /etc show e208226:ssh/sshd_configPermitRootLogin prohibit-password
PasswordAuthentication yesWrite it over the current file. tee rewrites the existing file in place, so its owner and mode stay as they are; in our test a mode 600 file stayed 600:
sudo git -C /etc show e208226:ssh/sshd_config | sudo tee /etc/ssh/sshd_config > /dev/nullCheck the service's config before reloading it (sudo sshd -t, sudo nginx -t), then commit the restore:
sudo etckeeper commit "Restore sshd_config from e208226"git checkout <commit> -- <file> and git restore write a new file using your umask. In our test a mode 600 file came back 644, and a deleted file that had been 640, like /etc/shadow, came back readable by everyone. After a checkout, run sudo etckeeper init to reapply the recorded owners and modes. It also stages all changes, so commit afterwards.
The recorded settings live in /etc/.etckeeper: a line such as maybe chmod 0640 './shadow' for every file, plus maybe chgrp 'shadow' './shadow' when the group isn't root. To see one file's entries:
sudo grep "'./shadow'" /etc/.etckeeperWhat etckeeper records
| Item | Recorded? |
|---|---|
| File contents and symlinks | Yes, by git |
| Executable bit | Yes, by git |
| Every file's mode, and owners and groups other than root | Yes, in /etc/.etckeeper, rewritten by the pre-commit hook on each commit |
| Empty directories | Yes, as mkdir -p lines in /etc/.etckeeper |
| ACLs and extended attributes | No; the metadata script skips them |
| Sockets, pipes, device files, and which files are hard links | No; etckeeper warns about them |
| Ignored files, and anything outside /etc | No |
Secrets: keep the repository private
/etc holds secrets, and git keeps every version of them: password hashes in /etc/shadow, SSH host private keys in /etc/ssh/, TLS keys under /etc/ssl/private and /etc/letsencrypt, VPN keys and passwords in application configs. etckeeper makes /etc/.git and /etc/.etckeeper readable by root only. Anyone who gets a copy of .git gets every secret it ever held.
Never push it to a public repository, and treat any hosted copy as you would /etc/shadow. To keep a copy elsewhere, the README pushes over SSH to a bare repository on a server you control, created with mode 700. root's SSH key must reach that server:
ssh backup1 'mkdir /srv/etc-web1 && chmod 700 /srv/etc-web1 && git init --bare /srv/etc-web1'sudo git -C /etc remote add backup ssh://backup1/srv/etc-web1sudo git -C /etc push backup --allPUSH_REMOTE="backup"in /etc/etckeeper/etckeeper.conf pushes after every commit. A failed push doesn't fail the commit, so check the remote now and then.- Encrypt off-site copies of the repository, for example inside an encrypted backup.
etckeeper initruns code stored in the repository, so only run it on clones you trust.- If a repository holding secrets leaks, rotate those secrets. Rewriting history doesn't recall a copy.
Why etckeeper is not a backup
The repository sits in /etc/.git, on the same disk as /etc: a dead disk, a deleted server or an attacker with root takes both. It covers only /etc, not your sites, databases or /home. Use it as the record of configuration changes and keep a real backup beside it: a tar archive of /etc and your data, copied off the server, as in the server backup checklist. A tar of /etc includes /etc/.git, so the history leaves the server too. Commit before a release upgrade and you can diff every config file the upgrade touched.
Common errors
| Message | Cause | Fix |
|---|---|---|
Auto packing the repository in background for optimum performance. (a daily cron mail) | git's automatic housekeeping, which etckeeper runs ten times more often than git's default | sudo git -C /etc config gc.auto 0, then run sudo git -C /etc gc --quiet now and then. In our test this stopped the message. |
** etckeeper detected uncommitted changes in /etc prior to apt run | AVOID_COMMIT_BEFORE_INSTALL=1 and uncommitted edits | Commit them with sudo etckeeper commit, then rerun apt. |
etckeeper warning: special files could cause problems with git: | A socket, pipe or device file in /etc | Move it or ignore it; AVOID_SPECIAL_FILE_WARNING=1 silences it. |
etckeeper warning: hardlinked files could cause problems with git: | Two names for one file; git stores two copies | Usually harmless; a symlink avoids it. |
warning: etckeeper failed to commit changes in /etc using git | The commit after apt failed | Run sudo etckeeper commit "test" to see git's error. |
etckeeper warning: etckeeper is not yet enabled for /etc | No /etc/.git | sudo etckeeper init, then commit. |
Please configure a VCS in /etc/etckeeper/etckeeper.conf | No VCS= line and no repository | Set VCS="git". |
fatal: not a git repository (or any of the parent directories): .git | git run in /etc without sudo | Add sudo. |
Frequently asked questions
- Does etckeeper back up /etc?
- It keeps a history of /etc in /etc/.git on the same disk, which helps with mistakes but not with losing the server. Copy /etc, including .git, off the server with a real backup.
- How do I see what changed in /etc yesterday?
sudo git -C /etc log --since=yesterday -plists each commit with its diff.sudo git -C /etc diffshows changes not committed yet.- Can I push etckeeper to GitHub?
- Only to a private repository, and only if you accept a third party holding your password hashes and private keys. A bare repository on a server you control, mode 700, is safer.
- How do I stop etckeeper's daily commits?
- Set
AVOID_DAILY_AUTOCOMMITS=1in /etc/etckeeper/etckeeper.conf for the cron job, and runsudo systemctl disable --now etckeeper.timerfor the timer.
How this was checked
Commands, limits and prices were checked against these official pages, on October 4, 2026:
- etckeeper README
- etckeeper(8) manual, 1.18.20 (Ubuntu 24.04)
- etckeeper 1.18.20 source in Debian (hooks, daily script, timer, apt.conf, postinst)
- etckeeper pre-commit.d/30store-metadata
- Debian bug #1149806: etckeeper.timer enabled by default
- Debian bug #1050413: daily auto-pack mail
- Ubuntu packages: etckeeper in Ubuntu 24.04
- Ubuntu packages: etckeeper in Ubuntu 22.04
- Debian packages: etckeeper in Debian 12
- Debian packages: etckeeper in Debian 13
- git-log documentation
- git-show documentation
- git-gc documentation: gc.auto