static.apur.in

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

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:

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.

← manuals