apt holds and pins, and how they rot
2026-07-14
Both mechanisms say "do not change this package". Neither says for how long, and neither says what it just stopped from being updated. That is the whole problem with them.
The difference that matters
- Hold (
apt-mark hold) is a per-package veto. The package is excluded from upgrades entirely, including security upgrades, and including upgrades that another package now requires. - Pin (
/etc/apt/preferences.d/) is a preference rule evaluated during dependency resolution. It can say "from this suite, only up to that version", which a hold cannot express.
A pin with Pin-Priority below 1000 still allows an install when nothing else
provides the dependency; a hold refuses regardless. When the reason is "this version works
and the next one broke the module", a hold is the honest tool. When the reason is "I want
the repository's version rather than the backports version", it is a pin.
How they rot
The failure is quiet in three ways:
- Security updates stop for that package and nobody is told.
unattended-upgradesskips held packages without an alarming log line, and the report says "0 packages upgraded", which reads like "nothing needed doing". - Dependencies get held transitively. Hold one library and every package that depends on its exact soname stops moving too. You now hold twenty packages to keep one still.
- The reason evaporates. A hold added to survive one bad release is still there two LTS point-releases later, long after the bug was fixed upstream, because whoever put it there is no longer thinking about it.
Auditing a machine you did not configure
apt-mark showhold
dpkg --get-selections | awk '/hold$/ {print $1}'
grep -rsH "" /etc/apt/preferences.d/ 2>/dev/null
apt-get -s upgrade | sed -n "/kept back/,+8p" # what apt refuses to move
unattended-upgrades --dry-run --debug 2>&1 | grep -i "hold\|blacklist" | head
Then for every entry that survives, find out whether it is still needed:
apt-cache policy nginx
# Installed / Candidate / Version table — if Candidate == Installed and the
# version is older than the security suite offers, the hold is costing you.
Making them survivable
If a hold or pin must exist, write down why and when it should be reconsidered. The file is the place, because that is the only place the next person will look:
# /etc/apt/preferences.d/nginx.pref
# Reason: 1.25 broke the stream preread variable we rely on (LP#xxxxxxx).
# Owner: admin@apur.in Added: 2026-03-02 Re-check: 2026-09-01
Package: nginx
Pin: version 1.24.*
Pin-Priority: 900
Prefer narrowing the window over freezing the version: pin to a version range, or pin by suite and keep the security pocket at priority 600 so CVE fixes still arrive while the feature release does not. And put the re-check date in a calendar — a comment in a file nobody opens is a note to your future self that will never be read.