skip to content

One package on a fleet of Linux servers must stay at a specific version while everything else keeps receiving updates. What do a hold and a version pin actually do on Debian- and RPM-family systems, and what tends to go wrong with them over time?

level: seniorimportance: should knowfreq 38%

answer

  1. hold is a state, pin is a priority
  2. priority above a thousand permits downgrade
  3. excluded is invisible, locked is constrained
  4. security updates stop without telling you
  5. freeze the snapshot, not the package

basics

~20 s

A hold is a per-package selection state saying "never change this"; a pin is a priority rule steering which candidate version wins. Both silently stop security updates for that package and can block unrelated upgrades once a dependency needs the newer version.

solid answer

~50 s

On Debian systems there are two distinct mechanisms. A **hold** is a selection state recorded in dpkg's own database — it means "do not change this package" and is not version-aware. A **pin** is a priority rule in `/etc/apt/preferences.d/`, matching a package against a version, release or origin and assigning a `Pin-Priority`; the resolver then picks the highest-priority candidate, and a priority above 1000 even permits a downgrade. On RPM systems, an `exclude` setting removes matching packages from the solver's view entirely, while the versionlock plugin records an exact version-release the solver must keep. The failure modes are the interesting part: security updates for the frozen package stop arriving and nobody is told; the pin eventually blocks the whole transaction when another package needs the newer version, so the solver either fails or offers to remove things; and an exclude is worse than a lock, because a package the solver cannot see at any acceptable version may be removed to satisfy something else.

code

bash · 7 lines
bash
# APT: pin one package to a version, strongly enough to downgrade to it
cat > /etc/apt/preferences.d/pin-myapp <<'EOF'
# Works around upstream regression FOO-123; review 2026-01-01
Package: myapp
Pin: version 1.24.0-*
Pin-Priority: 1001
EOF

go deeper

for a junior

Know that a package can be frozen at its current version, and that freezing it means it stops receiving updates — including security updates — until someone removes the freeze.

for a middle

Distinguish the mechanisms: a hold is a per-package selection state, a pin is a priority rule that decides which candidate version wins, and an exclude removes the package from the solver's view entirely.

for a senior

Anticipate the failure: the frozen package blocks an unrelated upgrade months later, or an exclude lets the solver remove it, and the engineer hitting it has no record of why the freeze exists. Trace and justify inherited pins rather than carrying them.

for a principal

Decide the fleet-wide policy: prefer a frozen repository snapshot that keeps the whole dependency set consistent, require every per-package exception to carry an owner and an expiry in configuration management, and report frozen packages inside the vulnerability picture.

## Two different ideas that get called the same thing "Pinning" in casual speech covers two mechanisms with different semantics, and choosing the wrong one is where teams get hurt. **A hold** is a boolean per-package state: this package must not change. On Debian systems it lives in dpkg's selection state, which means it is part of the host's package database rather than a configuration file — an important detail, because it does not travel with your configuration management unless you make it, and it is easy to miss when someone else debugs the host later. It is not version-aware: it freezes whatever is installed now. **A pin** is a priority. An entry in `/etc/apt/preferences.d/` names packages (by name, or by origin, release or version glob) and assigns a `Pin-Priority`. The resolver evaluates every candidate version and takes the highest-priority one. The scale matters: a negative priority means never install this version; 100 is roughly the weight of the version already installed; 500 is a normal available version; around 990 is the target release; above 1000 the pin is strong enough that the resolver will *downgrade* to satisfy it. The consequence is that a pin is directional and expressive — you can pin a whole third-party origin *down* so it may only supply packages you explicitly request — while a hold is a blunt instrument. On the RPM side the same split exists. `exclude` in the manager's configuration or a repository section removes matching packages from consideration completely, as if the repository never offered them. The versionlock plugin instead records an exact name-version-release constraint that the solver must honour. Excluding is a bigger hammer than locking: if the installed package needs to change for some other transaction to succeed and no acceptable version is visible, removal becomes a valid solution. ## Why this is usually the wrong shape of fix A pin is a promise about one node in a dense graph. Shared libraries mean the graph is release-wide, so freezing one node while everything around it moves is stable only for a while. **Security updates stop, silently.** The pinned package no longer receives fixes, and nothing in the update output says "by the way, you are three CVEs behind on this one". The freeze that was applied for two weeks to work around a regression is still there two years later, and it is invisible to the vulnerability report that only counts what the manager offers to upgrade. **The pin eventually blocks other work.** When another package's new version requires a newer version of the pinned one, the solver has no valid assignment. On Debian systems this surfaces as held packages preventing the upgrade; on RPM systems as a resolution failure or an offer to remove. The failure often lands on the engineer doing routine patching months later, with no context for why the pin exists. **Excludes can lead to removal.** Because an excluded package is invisible rather than fixed, the solver may satisfy someone else's conflict by removing it. A lock at least tells the solver what value is acceptable. **Pins are invisible archaeology.** A hold in the package database, an exclude in a config file and a versionlock list are three different places to look. On a fleet, the same freeze often exists in different forms on different hosts. ## The better-shaped alternatives **Freeze the repository, not the package.** Point hosts at a snapshot of the whole repository taken at a known date, and move the snapshot forward deliberately. Every host then solves against the same consistent universe, the dependency graph stays internally coherent, and "which versions are we running" has one answer instead of one per host. This is how large fleets get reproducibility without per-package exceptions. **Make the exception expire.** If a per-package freeze is genuinely needed, it belongs in configuration management with an owner, a reason and a review date — not typed into a shell on one machine. The pin file itself should carry a comment explaining what it works around. **Pin the origin, not the version.** For third-party repositories, pinning the whole origin to a low priority is often the real intent: "this vendor may supply their own package, but must never silently replace distribution packages." That solves a class of problems rather than one instance. **Or move the version decision into the artifact.** If a service needs an exact runtime version regardless of the host, shipping it as an image or a self-contained bundle takes the constraint out of the host's package graph entirely — a different model with its own costs, but it stops the host-level pin arms race. ## What to do when you inherit a pin Find all three places a freeze can live, work out what it was working around, check whether the upstream regression was fixed, and prefer removing it to carrying it. A frozen package that nobody can justify is a vulnerability with a note attached.

  • What is the practical difference between excluding a package and locking it to a version?
    A lock tells the solver which version is acceptable, so it plans around that constraint. An exclude makes the package invisible instead — the solver sees no acceptable version at all, and if another package's requirements collide, removing the excluded package becomes a legitimate solution. Locking states an intention; excluding hides information, and hidden information is what produces surprising plans.
  • Why do large fleets freeze a repository snapshot rather than individual packages?
    Because dependencies are release-wide. A snapshot freezes a set of versions that is internally consistent, so every host solves the same problem and gets the same answer, and moving forward is one deliberate action with one changelog. Per-package pins freeze one node in a graph that keeps moving around it, which drifts per host and eventually deadlocks.
  • How would you make sure a pin does not quietly outlive its reason?
    Keep it in configuration management, never typed on a host: a file with the package, the reason, the ticket and a review date, applied by the same system that applies everything else. Then report on pinned packages the way you report on unpatched ones, so a freeze shows up in the vulnerability picture instead of hiding from it.

saying these in an interview costs you the question

  • Assuming a pinned package still receives security updates
  • Treating exclude and version-lock as equivalent
  • Pinning one package and expecting the rest to upgrade freely
  • Leaving a hold on a host with no record of why
  • Believing a hold survives a distribution release upgrade unchanged

context