Usually you want every package updated. Sometimes you don’t: a database major version you aren’t ready to upgrade, a kernel that works with a specific driver, a PHP version your app needs, or a third-party repository that should only supply one package. Linux package managers let you hold packages at their current version, or pin which repository and version they come from.

Debian/Ubuntu: apt-mark hold (simplest)

sudo apt-mark hold mysql-server mysql-server-8.0
apt-mark showhold
sudo apt-mark unhold mysql-server mysql-server-8.0

Held packages are skipped by apt upgrade and unattended-upgrades. They’re shown as “kept back”.

Remember to unhold for security updates. A held package doesn’t get any fixes. Use holds as a short-term measure, and track them.

APT pinning with preferences

Pinning sets priorities, so APT chooses versions from specific repositories or version ranges. Files go in /etc/apt/preferences.d/.

Keep a package on a version series:

# /etc/apt/preferences.d/nodejs
Package: nodejs
Pin: version 20.*
Pin-Priority: 1001

Only take one package from a third-party repository (and nothing else from it):

# /etc/apt/preferences.d/vendor-repo
Package: *
Pin: origin repo.vendor.example
Pin-Priority: 100

Package: vendor-agent
Pin: origin repo.vendor.example
Pin-Priority: 600

Check the effect:

apt policy vendor-agent
apt policy                  # all repositories and their priorities

Pin priorities

Priority Effect
< 0 Never install this version
1–99 Only if no other version is installed (e.g. backports default: 100)
100 Installed version; won’t upgrade to it automatically from this source
500 Default for normal repositories
990 Default target release
> 1000 Install even if it means a downgrade

Use Pin-Priority: -1 to block a package entirely:

Package: snapd
Pin: release *
Pin-Priority: -1

See Debian sources.list and Debian backports.

RHEL / AlmaLinux / CentOS: dnf versionlock

sudo dnf install python3-dnf-plugin-versionlock
sudo dnf versionlock add mariadb-server
sudo dnf versionlock list
sudo dnf versionlock delete mariadb-server

Or exclude packages from updates in /etc/dnf/dnf.conf:

[main]
exclude=kernel* php*

Modules (on RHEL 8/9) pin whole software streams, for example dnf module enable php:8.2. See dnf and yum commands.

Docker images: pin versions too

The same idea applies to containers: use specific tags (postgres:16.4, or postgres:16), not latest, so a pull doesn’t upgrade a major version unexpectedly. See Watchtower auto-updates.

Good practices

  • Write down why a package is held or pinned (a comment in the preferences file, or your documentation), and when to revisit it.
  • Review holds regularly: apt-mark showhold, dnf versionlock list.
  • Watch security advisories for held packages, and plan the upgrade.
  • Prefer pinning a major version series (20.*) over an exact version, so you still get patch releases.

Troubleshooting

  • “The following packages have been kept back”: either a hold, or the upgrade needs new dependencies (apt full-upgrade handles that). Check apt-mark showhold first.
  • A pin doesn’t seem to work: run apt policy <pkg>, which shows every candidate version and its priority. Typos in the Pin: line (origin vs release) are the usual cause.
  • Dependency conflicts after pinning: a pinned package can block upgrades of things that depend on it. Pin the related packages together (for example all php8.2-* packages), or unpin.

Quick reference

Task Command
Hold (Debian/Ubuntu) apt-mark hold <pkg>
List holds apt-mark showhold
Pin by version or origin /etc/apt/preferences.d/*
Check priorities apt policy <pkg>
Lock (RHEL family) dnf versionlock add <pkg>
Exclude exclude= in dnf.conf

Related: apt commands and update self-hosted apps safely.

  • Linux
  • APT
  • Package Management