Skip to content

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:

dnf mark install libfoo libbar

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:

rpm --import https://rpm.example.com/RPM-GPG-KEY-example

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):

# Internal repo
[internal]
priority=10

# EPEL
[epel]
priority=90

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:

grep -r 'exclude=' /etc/yum.repos.d/ /etc/dnf/dnf.conf

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:

dnf reposync --repoid=appstream --download-metadata -p /srv/repos/

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.