DNF Footguns¶
Mistakes that cause failed deploys, outages, or data loss with dnf.
1. Module stream lock-in¶
You enable postgresql:15 to test something, then try to install postgresql:16 later.
DNF refuses — the stream is "locked" once enabled. Switching requires a disruptive reset.
# This fails after enabling :15
dnf module install postgresql:16
# Error: postgresql:16 stream is not enabled
# You have to reset first (removes module packages)
dnf module reset postgresql
dnf module enable postgresql:16
dnf module install postgresql:16
The reset can remove installed packages. On a running database server, this is catastrophic.
Fix: Plan module stream choices before deploying. Document which stream each server uses.
Never casually module enable in production without understanding the commitment. Test stream
switches in a disposable environment first.
2. dnf autoremove removing packages you need¶
dnf autoremove removes packages that were installed as dependencies but are no longer
required by any user-installed package. The problem: if you installed something with rpm -i
instead of dnf install, dnf doesn't track it as user-installed. Its dependencies become
autoremove candidates.
# You installed a package via rpm directly
rpm -i custom-app-1.0.rpm
# Later, autoremove kills its dependencies
dnf autoremove -y
# Removes libfoo, libbar that custom-app needs
# custom-app is now broken
Fix: Always install via dnf install ./package.rpm (note the ./ prefix) so dnf
tracks it properly. For existing systems, mark critical packages as user-installed:
3. Forgetting --security during a patch window¶
You meant to apply security patches only, but ran dnf update -y without --security.
Now you've pulled in feature updates, new major versions, and potentially breaking changes.
Fix: Always use dnf update --security -y during patch windows. Better yet, test with
dnf update --security --assumeno first to preview changes. Wrap it in automation that
enforces the flag.
4. GPG key import prompts breaking automation¶
First time installing from a new repo, dnf prompts to import the GPG key interactively. In CI/CD or Ansible, this blocks forever or fails.
Importing GPG key 0x12345678:
Userid : "Example Corp <security@example.com>"
Fingerprint: ABCD 1234 ...
Is this ok [y/N]:
Fix: Pre-import keys before running dnf:
Or in Ansible:
- name: Import repo GPG key
rpm_key:
state: present
key: https://rpm.example.com/RPM-GPG-KEY-example
For repos you control, set gpgkey= in the repo file and ensure the key is deployed
before the first dnf install.
5. Repo priority misconfiguration¶
You have EPEL enabled with priority=99 and your internal repo with no priority set.
Default priority is 99. Your internal nginx build and EPEL's nginx have the same
priority — dnf picks the one with the higher version, which might be EPEL's.
Fix: Set explicit priorities for all repos that might have overlapping packages. Internal repos should have lower numbers (higher priority):
Verify with dnf repoquery --showduplicates nginx to confirm which repo wins.
6. exclude= in repo config silently hiding security patches¶
Someone added exclude=kernel* to /etc/dnf/dnf.conf or a repo file to prevent
kernel updates. Six months later, a critical kernel CVE is disclosed. dnf update --security
reports "No packages needed for security" because the kernel packages are invisible.
Fix: Audit exclude lines regularly:
Use dnf versionlock instead of exclude= when you want to pin specific versions.
Versionlock shows up in dnf versionlock list and is harder to forget about.
7. Running dnf update on a system with broken RPM database¶
If the RPM database (/var/lib/rpm/) is corrupted (disk full during a previous transaction,
killed process), dnf operations fail with cryptic Berkeley DB or SQLite errors.
Fix: Rebuild the RPM database:
# Back up first
cp -a /var/lib/rpm /var/lib/rpm.backup
# Rebuild
rpm --rebuilddb
# Verify
rpm -qa | wc -l # should list all installed packages
On RHEL 9+, the RPM database is SQLite, which is more resilient. But corruption can still happen if a transaction is interrupted.
8. Not understanding --best vs --nobest¶
By default (RHEL 9), --best is enabled — dnf tries to install the latest version and
fails if dependencies can't be satisfied. Some admins add best=False to dnf.conf to
avoid errors, which means dnf silently installs older versions when the latest has dep issues.
# With best=True (default on RHEL 9): fails loudly
dnf install foo
# Error: nothing provides libbar >= 2.0 needed by foo-3.0
# With best=False: silently installs foo-2.0 instead
dnf install foo
# Installed: foo-2.0 (you didn't notice it wasn't 3.0)
Fix: Keep best=True (the default). Fix dependency issues rather than hiding them.
If you must use --nobest, log a warning and investigate why the latest version can't
be installed.
9. Forgetting to sync module metadata in offline repos¶
You mirror a repo with reposync but forget --download-metadata. The local repo has
the packages but no module metadata. Clients can't use dnf module commands and may
get unexpected package versions because module filtering doesn't apply.
Fix: Always use --download-metadata with reposync:
Verify by checking that modules.yaml exists in the repo metadata directory.
10. dnf clean all before reposync on a slow connection¶
dnf clean all wipes the metadata cache. If you then run reposync, it has to re-download
all metadata from scratch. On a slow or metered connection, this wastes bandwidth and time.
Fix: Only clean when you have a reason (stale metadata causing issues). Use
dnf clean expire-cache to mark cache as stale without deleting it — dnf will re-validate
on next use.
11. Mixing rpm -e with dnf-managed packages¶
Removing a package with rpm -e bypasses dnf's transaction tracking. DNF's history and
dependency tree become inconsistent. Later dnf update or dnf autoremove may behave
unpredictably.
Fix: Always use dnf remove to uninstall packages. If you must use rpm -e (e.g.,
for a circular dependency), run dnf history userinstalled afterward to verify the
state makes sense.
12. Assuming dnf history rollback restores config files¶
dnf history rollback reinstalls/downgrades/removes packages to match a previous state.
But it does not restore config files that were modified by %post scriptlets or by the
admin. You may roll back nginx to 1.24 but still have 1.26's config format.
Fix: Always back up /etc/ before major updates. Use etckeeper or a config management
tool (Ansible, Puppet) that can restore configs to a known state independently of package
versions.