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-upgradehandles that). Checkapt-mark showholdfirst. - A pin doesn’t seem to work: run
apt policy <pkg>, which shows every candidate version and its priority. Typos in thePin: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.