The scenario
You run sudo dnf upgrade on a Tuesday morning. The transaction pulls in a new version of a shared library your custom automation script depends on. The script breaks. You spend three hours chasing a missing symbol that only exists in the new release. You need a way to tell the package manager to leave specific packages alone while the rest of the system moves forward. A botched dependency update can leave a production service unresponsive. Pinning the right packages prevents that cascade.
What version locking actually does
DNF is designed to keep your system consistent by pulling in the newest available versions. It resolves dependencies automatically. That automatic behavior is excellent for security patches and feature updates. It becomes a liability when a specific application requires a frozen dependency tree. Version locking intercepts the dependency resolver. It marks specific package names and versions as immutable during transaction planning. The package manager still calculates the full upgrade path, but it treats the locked entries as fixed points.
Think of it like placing a heavy weight on a specific page of a manual. You can still flip through the rest of the pages, but that one page stays exactly where you put it. The lock does not stop the package manager from running. It only changes how the solver evaluates available updates. The lock persists across reboots and survives dnf clean all. It lives in a plain text file that the plugin reads before every transaction.
The DNF solver works in phases. It first gathers available packages from enabled repositories. It then builds a dependency graph. It finally selects the highest version for each package that satisfies all requirements. The versionlock plugin injects a filter step right after the repository metadata loads. It removes newer versions of locked packages from the candidate pool. The solver never sees them. This approach is safer than using the exclude directive in dnf.conf. Excludes remove packages from the solver entirely, which can break dependency chains for other software. Locks preserve the package in the database but freeze its version.
Run dnf versionlock list before you start pinning anything. Knowing your baseline prevents accidental conflicts later.
Setting up and applying locks
The version lock functionality ships as a separate plugin. Fedora does not enable it by default to avoid accidental dependency deadlocks. Install the plugin before attempting to pin anything.
Here is how to install the plugin and verify it is ready for use.
sudo dnf install dnf-plugin-versionlock
# The plugin registers itself with DNF automatically upon installation
# No manual configuration is required in dnf.conf
# The plugin reads locks from /etc/dnf/versionlock.list
Once the plugin is active, you can lock a package to its currently installed version. This is the safest approach because you do not need to look up the exact release string. DNF captures the version, release, and architecture from the installed RPM database.
Here is how to lock a package to its current state without typing the version string.
sudo dnf versionlock add firefox
# DNF queries the local RPM database for the installed version
# It appends the full NEVRA string to the versionlock list
# The lock applies to all future dnf upgrade transactions
You can also lock a package to a specific version string. This is useful when you know exactly which release your application requires, or when you want to force a downgrade and keep it there.
Here is how to lock a package to an explicit version string.
sudo dnf versionlock add firefox-120.0.1-1.fc39
# The plugin validates the NEVRA format before writing the lock
# It overwrites any existing lock for the same package name
# The lock survives package removals and reinstalls
The lock data lives in /etc/dnf/versionlock.list. You can view the current state of your pins at any time. The list shows the exact version strings that DNF will refuse to touch.
Here is how to display every active version lock on the system.
sudo dnf versionlock list
# The output mirrors the contents of /etc/dnf/versionlock.list
# Each line contains the package name and locked version
# An empty list means no packages are currently pinned
Convention aside: configuration files in /etc/ are meant for user modifications. Files in /usr/lib/ ship with packages and get overwritten during upgrades. The versionlock plugin respects this boundary. It only writes to /etc/dnf/. You can edit the list file manually, but using the CLI commands prevents syntax errors that break the parser. Each line must follow the name-version-release.arch format. A malformed line causes the plugin to skip the entire file.
Check the lock list after every bulk operation. Typos in the version string create silent failures.
Verifying the lock held
A lock is useless if DNF ignores it during a normal update cycle. Run a standard upgrade to confirm the solver respects the pin. You will see a specific message in the transaction summary.
Here is how to run an upgrade and verify the lock triggers.
sudo dnf upgrade --refresh
# The --refresh flag forces DNF to download fresh metadata
# This ensures you are testing against the actual repository state
# DNF will print a skipping message for each locked package
The output will contain a line similar to Skipping package firefox-120.0.1-1.fc39.x86_64, it is versionlocked. The transaction proceeds with all other available updates. The locked package remains untouched. If you do not see the skipping message, the plugin is either not installed or the lock was not applied correctly. Check the list again before proceeding.
Run dnf versionlock list immediately after the upgrade. The entry should still be there. If it disappeared, something else modified the file or the plugin failed to load. You can also verify the lock survived by checking the installed version with rpm -q firefox. The output must match your pinned version exactly.
Reboot before you debug. Half the time the symptom is gone after a clean transaction cycle.
When the lock fights back
Version locking changes how DNF solves dependencies. It does not remove dependency rules. If another package requires a newer version of your locked package, the solver will abort. DNF will not silently downgrade the dependent package to satisfy the lock. It will stop and ask for your intervention.
You will see an error that looks like this:
Problem: package gstreamer1-plugins-base-1.22.0-1.fc39.x86_64 requires firefox >= 121.0, but none of the providers can be installed.
- package firefox-120.0.1-1.fc39.x86_64 is filtered out by versionlock
The conflict is intentional. DNF is protecting you from a broken transaction. You have two choices. Remove the lock and let the upgrade proceed. Or find a compatible version of the dependent package that works with your pinned version. Forcing the transaction with --skip-broken will leave your system in a partially updated state. That approach creates more work later.
Convention aside: dnf upgrade --refresh is the normal weekly maintenance command. dnf system-upgrade is for crossing major Fedora releases. They are different commands. Version locking applies to both, but system-upgrade performs a full OS replacement. Pinning packages during a major release jump often breaks the transaction entirely. Clear your locks before running system-upgrade.
If the plugin appears inactive, check the DNF configuration. The plugin enables itself automatically, but a custom plugins=0 line in /etc/dnf/dnf.conf will disable it globally. Verify the setting matches your expectations.
Here is how to check whether the plugin is actually enabled in your configuration.
grep -i plugins /etc/dnf/dnf.conf
# The default value is plugins=1
# A value of plugins=0 disables all DNF plugins including versionlock
# Comment out the line or set it to 1 to restore plugin functionality
You can also inspect the DNF transaction logs to see why a lock was skipped or why a dependency conflict occurred. The logs live in /var/log/dnf.log. Search for versionlock to trace plugin activity.
Snapshot the system before the upgrade. Future-you will thank you when the solver throws a dependency conflict.
Locking versus other DNF strategies
Pinning packages is one tool among many. Choose the right approach based on your actual workflow.
Use versionlock when you need to freeze a specific package version while allowing the rest of the system to update normally. Use the exclude directive in dnf.conf when you want to permanently ignore a package across all transactions without tracking exact versions. Use dnf downgrade when you need to temporarily revert a package to test compatibility, but want to return to the latest version later. Use containerization when your application requires a completely isolated dependency tree that never touches the host system. Stay on the default DNF behavior if you only deviate from the repository versions occasionally.