Fedora

DNF5 Cheatsheet for Fedora 45 / 44: Tested Commands and Fixes

Fedora 45 moved its own repo files out of /etc/yum.repos.d under the RelocateRpmRepoConfigsToUsr change, one of the quieter entries in the Fedora 45 changes. A fresh install shows that directory empty, yet dnf still finds the fedora, updates and openh264 repos, because they now ship under /usr/share/dnf5/repos.d with their keys under /usr/share/pki/rpm-gpg. Scripts that grep the old path stop matching anything.

Original content from computingforgeeks.com - post 167920

This dnf5 commands cheat sheet starts with the one-page reference and then explains the commands we use daily: search, installs and swaps, weak dependencies, kernels, advisories, versionlock, groups, repos and COPR, key imports, history undo and replay, offline updates, download tuning and the errors readers actually search for. We confirmed every command working on a fresh Fedora 45 Cloud VM with dnf5 5.4.7.0 in November 2026, and the Fedora 44 differences come from our October run on a Fedora 44 VM.

DNF5 commands cheat sheet

Every row below ran on the Fedora 45 lab VM, a few install and upgrade rows only as far as the plan with --assumeno, and the sections after the tables show the output and the catch behind each one. Package names are the ones we used in the lab, so swap in your own.

Search and inspect packages

CommandWhat it does
dnf search htopSearch package names and summaries
dnf info htopVersion, repo, size and URL of a package
dnf provides "*/bin/dig"Find which package ships a file
dnf repoquery --requires htopList what a package depends on
dnf repoquery --installed --whatrequires libcurlInstalled packages that need libcurl
dnf repoquery --installonlyKernels still on disk
dnf changelog htopPackage changelog (downloads extra metadata)
dnf leavesPackages that nothing else requires
dnf statusCounts of groups, packages and enabled repos

Install, remove and swap

CommandWhat it does
sudo dnf install -y fishInstall a package and its dependencies
sudo dnf install --assumeno cockpitShow the transaction, install nothing
sudo dnf install --setopt=install_weak_deps=False cockpitInstall without weak dependencies
sudo dnf remove -y fishRemove a package and its unused dependencies
sudo dnf autoremove -yRemove leftover dependencies
sudo dnf reinstall -y htopReinstall the same build
sudo dnf downgrade -y python3-dbusGo back to an older build in the repos
sudo dnf mark -y user ncurses-termKeep a dependency after its parent goes
sudo dnf swap -y ffmpeg-free ffmpeg --allowerasingReplace one package with another

Updates, advisories and kernels

CommandWhat it does
dnf check-upgrade --refreshList pending updates, exit code 100 if any
dnf advisory listAdvisories for installed packages
dnf advisory list --security --allSecurity advisories, applied ones too
dnf advisory summaryAdvisory counts by type and severity
sudo dnf upgrade --securityApply security updates only
sudo dnf upgrade -y --advisory=FEDORA-2026-f2ecc7c470Apply one advisory by ID
sudo dnf upgrade-minimalLowest versions that fix advisories
sudo dnf distro-syncMatch every package to the enabled repos
sudo dnf remove --oldinstallonly --limit=2Keep only the newest two kernels

Versionlock and groups

CommandWhat it does
sudo dnf versionlock add bind-libsHold a package at its installed version
dnf versionlock listShow the active locks
sudo dnf versionlock delete bind-libsRelease a lock
dnf group listGroups with their IDs
dnf group info development-toolsPackages in a group
sudo dnf group install -y development-toolsInstall a group by ID
sudo dnf install -y @development-toolsSame group through plain install
sudo dnf group remove -y development-toolsRemove the group’s packages
dnf environment listDesktop and server environments
sudo dnf environment install kde-desktop-environmentInstall an environment by ID

Repos and COPR

CommandWhat it does
dnf repo listEnabled repositories
dnf repo info fedoraRepo details and the file that defines it
sudo dnf config-manager addrepo --from-repofile=https://cli.github.com/packages/rpm/gh-cli.repoAdd a vendor repo file
sudo dnf config-manager enable updates-testingEnable a repo through an override
sudo dnf config-manager disable updates-testingDisable a repo through an override
sudo dnf config-manager unsetopt updates-testing.enabledDrop the override, back to the repo file
sudo dnf config-manager setopt gh-cli.priority=10Store a repo option
dnf --disable-repo=gh-cli repo listSkip a repo for one command
dnf --repo=gh-cli list --availableUse only one repo for one command
sudo dnf copr enable -y atim/starshipEnable a COPR project
sudo dnf copr remove atim/starshipDelete the COPR repo file

History, offline and config

CommandWhat it does
dnf history listTransactions with IDs and command lines
dnf history info lastPackages and reasons in the last transaction
sudo dnf history undo -y lastReverse the last transaction
sudo dnf install -y --store=/tmp/devtools zsh fishSave a transaction with its RPMs, install nothing
sudo dnf replay -y /tmp/devtoolsRun a stored transaction
sudo dnf upgrade -y --offline python3-dbusStage an update for the next boot
sudo dnf offline statusShow the staged transaction (needs root)
sudo dnf offline rebootReboot and apply the staged transaction
sudo dnf offline logBoots that ran an offline transaction
dnf --dump-main-configMerged main options and their values
sudo dnf --disable-plugin=systemd-inhibit reinstall -y libdnf5-plugin-systemd-inhibitRepair a broken plugin install

The PDF holds the same tables on one landscape page. The sections below take each group in turn, with the real output we got and the behaviour that a one-line table cannot show, so read the section before you put a row into a script.

Check what changed for dnf5 on Fedora 45

Both releases run the dnf5 5.4 series from their update repos, so the commands below work on both, though Fedora 44 can trail by a build. The differences come from where Fedora 45 packages its repo definitions:

ItemFedora 44Fedora 45
Fedora repo files/etc/yum.repos.d//usr/share/dnf5/repos.d/ (the /etc directory starts empty)
Fedora signing keys/etc/pki/rpm-gpg//usr/share/pki/rpm-gpg/
Repos you add with config-manager addrepo, COPR or RPM Fusion/etc/yum.repos.d//etc/yum.repos.d/ (unchanged)
Enabled repos on a fresh installfedora, updates, openh264fedora, updates, openh264
Repo overrides from config-manager/etc/dnf/repos.override.d/99-config_manager.repoSame

That table describes fresh installs, which is all we tested. The change page says repo files you edited on a Fedora 44 box stay in /etc/yum.repos.d after the upgrade and override the copies under /usr, so an upgraded system will not start with an empty directory.

Recent dnf5 releases also added commands that older cheat sheets miss: dnf status, dnf remove --oldinstallonly, and config-manager enable and disable. On the lab VM, the regular update stream moved dnf5 from 5.4.6.0 to 5.4.7.0 in the first week of November, so run sudo dnf upgrade --refresh before you compare your output with ours.

Check the dnf5 build and its plugins

On both releases dnf and yum are symlinks to dnf5. Confirm that and list the loaded plugins:

readlink -f /usr/bin/dnf /usr/bin/yum
dnf --version | grep -vE "^ +(version|API version):|^$"

The grep drops the per-plugin version lines. After the November update the VM loaded nine dnf5 plugins and three libdnf5 plugins:

/usr/bin/dnf5
/usr/bin/dnf5
dnf5 version 5.4.7.0
dnf5 plugin API version 2.0
libdnf5 version 5.4.7.0
libdnf5 plugin API version 2.2
Loaded dnf5 plugins:
  name: builddep
  name: changelog
  name: config-manager
  name: copr
  name: deptree
  name: needs_restarting
  name: repoclosure
  name: repomanage
  name: reposync
Loaded libdnf5 plugins:
  name: systemd-inhibit
  name: appstream
  name: expired-pgp-keys

Two of those were not there on day one. deptree arrived with the 5.4.7.0 dnf5-plugins update, and appstream came in as a dependency when we installed Cockpit. The plain Cloud image loads only systemd-inhibit, which blocks shutdown while a transaction runs, and expired-pgp-keys, which removes expired repo keys so a renewed key can be imported. If your list looks different, a package you installed brought its own plugin. For a one-screen summary of the box, run the status command:

dnf status

The lab VM is a Cloud Edition image, which shows as its one installed environment:

Updating and loading repositories:
Last metadata expiration check: 0:05:46 ago on 2026-11-06 06:24:32.
Repositories loaded.
Groups               : 50
  installed          : 1
  available          : 49
Environments         : 21
  installed          : 1
  available          : 20
Packages             : 83991
  installed          : 580
  available          : 83411
  user-installed     : 81
Installed Size       : 1.2 GiB
Enabled Repositories : 3
Last Transaction     : 2026-11-06 06:29:05

Three enabled repositories is the default on both releases: fedora, updates and the Cisco openh264 repo. The fedora-updates-testing.repo file ships on Fedora 45 as well, but it stays disabled on the release images, so a box that reports four enabled repos has had testing turned on by someone. That matters later, because config-manager enable leaves an override behind that outlives the reason it was added.

Find where Fedora 45 keeps repos and config

dnf5 reads repo files from three directories, in this order:

dnf --dump-main-config | grep -E "^(reposdir|varsdir) "

The search path already includes the /usr location. Fedora 45 is the first release to put its own files there:

reposdir = /etc/yum.repos.d,/etc/distro.repos.d,/usr/share/dnf5/repos.d
varsdir = /usr/share/dnf5/vars.d,/etc/dnf/vars

List both repo directories and the Fedora defaults file, then ask dnf which file defines the fedora repo:

ls /etc/yum.repos.d/ /usr/share/dnf5/repos.d/
cat /usr/share/dnf5/libdnf.conf.d/20-fedora-defaults.conf
dnf repo info fedora | grep -E "^(Repo ID|Config file)"

On the fresh VM, /etc/yum.repos.d is empty and all four Fedora repo files sit under /usr. The defaults file explains why a dead third-party repo is only a warning (skip_if_unavailable=True) and why dnf sometimes installs an older candidate (best=False):

Fedora 45 dnf5 repo files under /usr/share/dnf5/repos.d with an empty /etc/yum.repos.d and the Fedora defaults drop-in

Do not edit the files under /usr/share/dnf5, because the next package update overwrites them without a .rpmnew or any warning. Changes go in three places instead, and the repo and tuning sections below use all three:

  • /etc/dnf/dnf.conf for main options, which wins over the drop-ins
  • drop-ins under /etc/dnf/libdnf5.conf.d/, which survive package updates and are easy to ship with configuration management
  • per-repo overrides that config-manager writes to /etc/dnf/repos.override.d/

Third-party release packages still use /etc: the rpmfusion-free-release-45-0.2 package wrote its three repo files to /etc/yum.repos.d. A script that inventories repos therefore has to read both directories, or better, ask dnf repo info for the config file as we did above.

Search and inspect packages

Four read-only commands answer most “is it packaged, and what is it” questions, and none of them need root:

dnf search htop
dnf info htop
dnf provides /usr/bin/htop
dnf provides "*/bin/dig"

The provides glob is the quickest way to find which package ships a binary you do not have yet. On Fedora 45 it maps dig to two packages, the regular bind-utils and the newer bind9-next-utils, each listed once per repo that carries it. The first match is the installed copy:

Updating and loading repositories:
Repositories loaded.
bind-utils-32:9.18.50-35.fc45.x86_64 : Utilities for querying DNS name servers
Repo         : @System
Matched From :
Provide      : bind-utils = 9.18.50-35.fc45

Run as a normal user, dnf keeps its own metadata cache in ~/.cache/libdnf5. On the lab VM that cache reached 115 MB next to 80 MB in /var/cache/libdnf5, and the first dnf changelog call spent almost two minutes pulling the extra changelog metadata. That user cache also matters for update checks, as the next section shows. For dependency questions, repoquery does the walking:

dnf repoquery --requires htop
dnf repoquery --installed --whatrequires libcurl
dnf repoquery --installonly
dnf changelog htop

The --installonly query lists every kernel still on disk, which is the first thing to check when /boot fills up. A fresh Fedora 45 Cloud VM carries just one:

kernel-core-0:7.2.15-300.fc45.x86_64
kernel-modules-core-0:7.2.15-300.fc45.x86_64

Each kernel shows up as a kernel-core and kernel-modules-core pair, so count pairs, not lines, when you compare the list with installonly_limit.

Install, remove and swap packages

A plain install prints the transaction table first, then splits the run into numbered download, verify, prepare and install steps:

sudo dnf install -y fish

The table separates what you asked for from regular and weak dependencies. Here man-pages came in only because fish recommends it:

dnf5 install transaction for fish on Fedora 45 showing dependencies and a weak dependency

Weak dependencies are where most install bloat comes from, so we asked dnf to plan a Cockpit install twice, once as-is and once with weak dependencies off, and let it stop at the confirmation both times:

sudo dnf install --assumeno cockpit
sudo dnf install --assumeno --setopt=install_weak_deps=False cockpit

The first plan pulled 27 packages, among them PackageKit, AppStream data and the libdnf5-plugin-appstream plugin. The second needed 6. Cut down to the summary lines, the default plan comes first:

Transaction Summary:
 Installing:        27 packages

Total size of inbound packages is 28 MiB. Need to download 28 MiB.
After this operation, 41 MiB extra will be used (install 41 MiB, remove 0 B).
Operation aborted by the user.
---
Transaction Summary:
 Installing:         6 packages

Total size of inbound packages is 5 MiB. Need to download 5 MiB.
After this operation, 8 MiB extra will be used (install 8 MiB, remove 0 B).
Operation aborted by the user.

On servers, set install_weak_deps=False in a drop-in under /etc/dnf/libdnf5.conf.d/ (the tuning section shows the format) rather than passing it every time, because a flag that one admin forgets brings the 21 extra packages straight back. On a desktop we leave weak dependencies on, since that is how man pages, fonts and codecs helpers arrive. Removing a package also removes the dependencies it pulled in, because clean_requirements_on_remove defaults to on:

sudo dnf remove -y fish
sudo dnf autoremove -y
sudo dnf reinstall -y htop

Pass -y to autoremove in scripts. Without it, dnf asks for confirmation, and with no terminal attached it answers no and prints “Operation aborted by the user”, so a cron job looks like it ran while it removed nothing. The same applies to dnf mark, which runs as a transaction. Use it when you want to keep one dependency after the parent goes:

sudo dnf install -y fish
sudo dnf mark -y user ncurses-term
sudo dnf remove -y fish
rpm -q ncurses-term
dnf repoquery --installed --qf "%{name} %{reason}\n" ncurses-term

With the mark in place, removing fish took three packages instead of four and left ncurses-term behind. The last two commands confirm it’s still installed and now recorded as user-installed:

ncurses-term-6.6-3.fc45.noarch
ncurses-term User

sudo dnf mark -y dependency ncurses-term reverses it, and the next autoremove cleaned it up. To see what’s actually on the box without the dependency noise, use leaves:

dnf leaves | head -14

Leaves are packages that nothing else requires. A line starting with a dash opens a group, and the indented lines under it belong to the same dependency cycle: they need each other, and nothing else needs them. They are not dependencies of the dashed package:

- audit-0:4.2.1-2.fc45.x86_64
  audit-rules-0:4.2.1-2.fc45.x86_64
- bind-utils-32:9.18.51^git20261003.d0a87358-12.fc45.x86_64
- btrfs-progs-0:7.1-1.fc45.x86_64
- chrony-0:4.9-1.fc45.x86_64
- cloud-init-0:26.2-2.fc45.noarch
- cloud-utils-growpart-0:0.34-2.fc45.noarch
- console-login-helper-messages-issuegen-0:0.22.0-2.fc45.noarch
- console-login-helper-messages-motdgen-0:0.22.0-2.fc45.noarch
- console-login-helper-messages-profile-0:0.22.0-2.fc45.noarch
- dnf5-0:5.4.7.0-1.fc45.x86_64
  dnf5-plugins-0:5.4.7.0-1.fc45.x86_64
- dracut-config-generic-0:111-5.fc45.x86_64
- efibootmgr-0:18-13.fc45.x86_64

swap replaces one package with another in a single transaction. The common case on Fedora is moving from ffmpeg-free to the full build after enabling the RPM Fusion repositories for codecs:

sudo dnf swap -y ffmpeg-free ffmpeg --allowerasing

The --allowerasing flag lets dnf remove the seven -free libraries that conflict with RPM Fusion’s builds. The swap also imported the RPM Fusion key on first use, which -y accepted:

Package             Arch   Version                             Repository          Size
Removing:
 ffmpeg-free        x86_64 0:9.0.2-3.fc45                      updates          2.6 MiB
Removing dependent packages:
 libavcodec-free    x86_64 0:9.0.2-3.fc45                      updates         11.2 MiB
 libavdevice-free   x86_64 0:9.0.2-3.fc45                      updates        244.3 KiB
 libavfilter-free   x86_64 0:9.0.2-3.fc45                      updates          4.3 MiB
 libavformat-free   x86_64 0:9.0.2-3.fc45                      updates          2.9 MiB
 libavutil-free     x86_64 0:9.0.2-3.fc45                      updates        983.4 KiB
 libswresample-free x86_64 0:9.0.2-3.fc45                      updates        155.2 KiB
 libswscale-free    x86_64 0:9.0.2-3.fc45                      updates          1.1 MiB
Installing:
 ffmpeg             x86_64 0:9.0.2-3.fc45                      rpmfusion-free   2.6 MiB
Installing dependencies:
 ffmpeg-libs        x86_64 0:9.0.2-3.fc45                      rpmfusion-free  23.9 MiB
 libavdevice        x86_64 0:9.0.2-3.fc45                      rpmfusion-free 211.5 KiB
 ocl-icd            x86_64 0:2.3.5-1.fc45                      fedora         236.2 KiB
 vvenc-libs         x86_64 0:1.14.0-1.fc45                     rpmfusion-free   3.3 MiB
 x264-libs          x86_64 0:0.165-10.20250608gitb35605ac.fc45 rpmfusion-free   1.9 MiB
 x265-libs          x86_64 0:4.3-1.fc45                        rpmfusion-free  16.9 MiB
Installing weak dependencies:
 vmaf-models        noarch 0:3.2.1-1.fc45                      fedora           4.2 MiB

Transaction Summary:
 Installing:         8 packages
 Removing:           8 packages

One RPM Fusion detail caught us here. The Fedora 45 release package we installed (rpmfusion-free-release-45-0.2) enabled rpmfusion-free and rpmfusion-free-updates-testing and left rpmfusion-free-updates off, so check dnf repo list after you add it and flip the two with config-manager if you want only tested updates.

Keep the right number of kernels

Fedora keeps three kernels (installonly_limit = 3) and prunes the oldest when a fourth arrives. To trim earlier, for example on a small /boot, newer dnf5 has a dedicated flag. We installed the older 7.2.11 kernel from the release repo next to the running 7.2.15 and tried the command without a limit and with two limits:

sudo dnf remove --oldinstallonly
sudo dnf remove --assumeno --oldinstallonly --limit=2
sudo dnf remove -y --oldinstallonly --limit=1

With two kernels on disk, the first two commands both printed “Nothing to do.” Without --limit, the command only removes kernels beyond installonly_limit. Only --limit=1 had work to do, and it removed the older kernel and kept the running one:

Package              Arch   Version           Repository      Size
Removing:
 kernel-core         x86_64 0:7.2.11-300.fc45 fedora     101.3 MiB
 kernel-modules-core x86_64 0:7.2.11-300.fc45 fedora      46.9 MiB

Transaction Summary:
 Removing:           2 packages

After this operation, 148 MiB will be freed (install 0 B, remove 148 MiB).
Complete!

Keep at least two kernels on a real machine, because the second one is your way back when a new kernel won’t boot. The --limit=1 run above is fine on a throwaway VM, but on a laptop it removes the only fallback entry in the boot menu. So we stop at --limit=2 and let installonly_limit handle the pruning on every kernel update.

Apply updates and security advisories

check-upgrade exits with code 100 when updates are waiting and 0 when the system is current, which makes it the right test for scripts and monitoring:

dnf check-upgrade --refresh
echo "exit code: $?"

Keep --refresh in that check when it runs as a normal user. On the lab VM the plain command, run from the user account, read the per-user cache and listed 8 updates. The same command with --refresh fetched new metadata and listed 20, including the dnf5 5.4.7.0 update itself. Both exited 100, so a script would have said “updates pending” either way, but anything that counts or names the packages was working from stale data:

dnf check-upgrade as a normal user listing 8 updates from a stale cache, then 20 updates with --refresh on Fedora 45

Fedora publishes each update as an advisory. List the ones that apply to installed packages, add --security to filter, or add --all to include those already installed:

dnf advisory list
dnf advisory list --security --all
dnf advisory summary

The summary counted 10 pending advisories, three of them security fixes:

Available advisory information summary:
Security    : 3
  Critical  : 0
  Important : 2
  Moderate  : 1
  Low       : 0
  Other     : 0
Bugfix      : 2
Enhancement : 3
Other       : 2

Apply one advisory by ID, or every security fix with --security:

sudo dnf upgrade -y --advisory=FEDORA-2026-f2ecc7c470
sudo dnf upgrade --security

The advisory above is the curl fix, and it upgraded exactly curl and libcurl. That makes advisory IDs the safest unit for a change window, because the scope is fixed by Fedora and not by whatever else landed in the repos that morning. The three commands that apply “some” updates differ more than their names suggest. On the same box, upgrade --security planned 5 packages, upgrade-minimal planned 12, and distro-sync planned all 20. The packages upgrade-minimal left out were exactly the ones whose advisories carry the type “unspecified”, which included the dnf5 update:

sudo dnf upgrade-minimal
sudo dnf distro-sync

distro-sync aligns every package with what the enabled repos ship, downgrades included. Run it after disabling a third-party repo whose packages outrank the Fedora builds, because a plain upgrade never moves a package to a lower version. Read the plan with --assumeno first, since a sync on a box with leftover repo packages can list downgrades you didn’t expect.

Pin a package with versionlock

Versionlock holds a package at its installed version. It’s built into the main dnf5 package, so there is nothing to install. We downgraded bind-libs and bind-utils to have a pending update, then locked the library:

sudo dnf versionlock add bind-libs
dnf versionlock list
sudo cat /etc/dnf/versionlock.toml

The lock lands in a TOML file, one [[packages]] table per lock, with the epoch included in the version:

version = "1.0"

[[packages]]
name = "bind-libs"
comment = "Added by 'versionlock add' command on 2026-11-06 06:29:01"

[[packages.conditions]]
key = "evr"
comparator = "="
value = "32:9.18.50-35.fc45"

The lock also holds back anything that needs the newer version. An upgrade of both packages skipped bind-utils, because the new build requires the matching bind-libs:

dnf5 versionlock add bind-libs and the upgrade skipping bind-utils with broken dependencies on Fedora 45

Read the last “Problem” line in that output. It says the newer bind-libs “is filtered out by exclude filtering”, yet excludepkgs was empty on the box and the lock was the only filter in place. When you see that phrase with no excludes configured, check dnf versionlock list first. Remove it with sudo dnf versionlock delete bind-libs, and the next upgrade took both packages. The dnf versionlock guide for Fedora covers wildcard locks and excluding a version instead of pinning one.

Install groups by ID, not by name

Groups bundle packages for a role, and environments bundle groups into a full system type. Always use the group ID in commands:

dnf group list
dnf group info development-tools
sudo dnf group install -y development-tools
sudo dnf group remove -y development-tools

On the lab VM, which already had git, development-tools installed 83 packages (776 MiB), and group remove took the same 83 back out. That clean reversal works because dnf records the group itself as installed, so the remove knows exactly which packages came in with it. The display name is a different story. dnf group info "Development Tools" found the group, but both install forms we tried with the name failed:

dnf5 group install Development Tools by display name failing with No match for argument on Fedora 45

The first command fails for a simpler reason: plain install needs an @ in front of a group, and sudo dnf install --assumeno @development-tools resolved the same 83 packages. Old Fedora guides still print the display name, so translate it with dnf group list before you paste their commands. Environments work the same way one level up:

dnf environment list
dnf environment info workstation-product-environment

The Fedora 45 list carries 20 environments (21 rows, see below), including COSMIC, Miracle WM and Sway alongside Workstation and KDE. The first rows show the ID to pass to dnf install @<id>:

ID                                Name                                 Installed
custom-environment                Fedora Custom Operating System              no
workstation-product-environment   Fedora Workstation                          no
server-product-environment        Fedora Server Edition                       no
cloud-server-environment          Fedora Cloud Server                        yes
cloud-server-environment          Fedora Cloud Server                         no
kde-desktop-environment           KDE Plasma Workspaces                       no

The installed environment shows twice, once from the system state and once from the repo metadata. That’s cosmetic, so don’t try to remove the duplicate. Installing workstation-product-environment on a Server or Cloud install is the dnf route to the full GNOME desktop without reinstalling, and the Fedora 45 Server and Workstation install guide shows what each edition starts with.

Installing one takes the same ID, this time with dnf environment install. We pulled KDE Plasma onto a Fedora 44 Cloud VM that had no desktop at all, which is the worst case for download size, and added -y in the lab only to skip the confirmation prompt:

sudo dnf environment install kde-desktop-environment

dnf first prints Group "core" is already installed. plus one “is already installed” line for each of the 58 packages the image already had, then the full package list. The part worth reading before you answer the prompt sits at the bottom, where the groups the environment drags in are listed by display name above the size totals:

Installing groups dependencies:
 Standard
 Printing Support
 Common NetworkManager Submodules
 Multimedia
 LibreOffice
 KDE PIM
 KDE Multimedia support
 KDE
 KDE Applications
 Input Methods
 Hardware Support
 Guest Desktop Agents
 Fonts
 Firefox Web Browser
 Dial-up Networking Support
 Desktop accessibility
 base-graphical
 Administration Tools
Installing environmental groups:
 KDE Plasma Workspaces

Transaction Summary:
 Installing:      1759 packages
 Upgrading:          3 packages
 Replacing:          3 packages

Total size of inbound packages is 3 GiB. Need to download 3 GiB.
After this operation, 7 GiB extra will be used (install 7 GiB, remove 2 MiB).

That’s 3 GiB to fetch and 7 GiB on disk, so check free space on / first. The download alone took just over 10 minutes on our lab link before the install phase started. A Workstation install already has most of the graphical base in place, so expect a smaller transaction there. Afterwards dnf environment list --installed showed kde-desktop-environment as installed next to the Cloud Server environment, yet systemctl get-default still returned multi-user.target, so the next boot would land on a text console. The install doesn’t touch the default systemd target:

sudo systemctl set-default graphical.target
sudo systemctl reboot

After the reboot, systemctl is-active display-manager.service returned active and the display manager link pointed at plasmalogin.service, the Plasma Login Manager. You only need that step on Server, Cloud or minimal installs. On Workstation, GDM already runs and Plasma simply shows up as another session on the login screen.

Add, disable and override repos

config-manager addrepo downloads a vendor’s repo file into /etc/yum.repos.d. The GitHub CLI repo is a good test because it uses one URL for every Fedora release:

sudo dnf config-manager addrepo --from-repofile=https://cli.github.com/packages/rpm/gh-cli.repo

Enable and disable work on any repo, including the Fedora ones under /usr. dnf does not touch the original file and writes an override instead. Turning on updates-testing to try a pending fix is the common case:

sudo dnf config-manager enable updates-testing
cat /etc/dnf/repos.override.d/99-config_manager.repo

The override file holds only the changed key:

# Generated by libdnf5.
# Do not modify this file manually, use dnf5 config-manager instead.
[updates-testing]
enabled=1

That line pins the repo on until you remove it, and config-manager disable does not remove it either: it rewrites the value to enabled=0. To hand control back to the repo file, unset the key:

sudo dnf config-manager unsetopt updates-testing.enabled

setopt stores any repo option the same way, such as a priority, and unsetopt clears it. Pass several keys to unsetopt to clear them in one go. In our run updates-testing.enabled was already gone, so dnf printed “Request to remove repository option but repoid is not present in the overrides: updates-testing”, still removed gh-cli.priority and exited 0:

sudo dnf config-manager setopt gh-cli.priority=10
sudo dnf config-manager unsetopt gh-cli.priority updates-testing.enabled

For a single command, the repo flags skip the config entirely. dnf5 accepts both the old --disablerepo spelling and the new --disable-repo, and both hid gh-cli from repo list on the lab VM:

dnf --disable-repo=gh-cli repo list
dnf --repo=gh-cli list --available

COPR repos have their own subcommand. disable keeps the repo file with enabled=0, while remove deletes it:

sudo dnf copr enable -y atim/starship
sudo dnf install -y starship
dnf copr list
sudo dnf copr disable atim/starship
sudo dnf copr remove atim/starship

Check the release tag on COPR packages. The starship build we got was 1.24.2-1.fc44, a Fedora 44 build served to Fedora 45, which is normal while a COPR owner hasn’t rebuilt for the new release. It usually works for a static binary like starship, but a package linked against libraries that changed between releases can fail at runtime, so treat an older tag as a reason to test before you roll it out. For more repo examples, including vendor repos with $releasever, see our guide to adding repositories with dnf5 config-manager, and the Fedora 45 post-install checklist lists the repos most desktops add first.

Import repo signing keys without aborting the install

The first install from a new repo asks you to trust its signing key. Some vendors ship more than one key in the same file, and dnf asks about each one. The GitHub CLI keyring holds the current key and one that expired on September 5, 2026, and the package is signed with the current key. We accepted the first prompt and declined the second:

sudo dnf install gh

Declining the expired key aborted the whole install, even though the key that signed the package was already accepted:

Importing OpenPGP key 0x62313325:
 UserID     : "GitHub CLI <[email protected]>"
 Fingerprint: 7F38BBB59D064DBCB3D84D725612B36462313325
 From       : https://cli.github.com/packages/githubcli-archive-keyring.asc
Is this ok [y/N]: Importing OpenPGP key 0x75716059:
 UserID     : "GitHub CLI <[email protected]>"
 Fingerprint: 2C6106201985B60E6C7AC87323F3D4EA75716059
 From       : https://cli.github.com/packages/githubcli-archive-keyring.asc
Is this ok [y/N]: Transaction failed: Signature verification failed.
OpenPGP check for package "gh-2.103.3-1.x86_64" (/var/cache/libdnf5/gh-cli-514f8955f024cc0d/packages/gh_2.103.3_linux_amd64.rpm) from repo "gh-cli" has failed: Canceled by the user.

Nothing was imported and gh wasn’t installed, because dnf imports keys only when the whole transaction goes through. Answer yes to every key from a repo you trust, or run the install with -y, which imported both keys. Check the fingerprints against the vendor’s published keys first if the repo is new to you, since -y accepts whatever the repo file points at. The expired key does not stay for long. On the very next transaction the expired-pgp-keys plugin removed it without a prompt:

The following OpenPGP key (0x75716059) is about to be removed:
 Reason     : Expired on 2026-09-05 12:43:48
 UserID     : "GitHub CLI <[email protected]>"
 Fingerprint: 2C6106201985B60E6C7AC87323F3D4EA75716059

As a result, installing packages signed with this key will fail.
It is recommended to remove the expired key to allow importing
an updated key. This might leave already installed packages unverifiable.

The system will now proceed with removing the key.
Key 0x75716059 was successfully removed.

To audit which keys the system trusts, list the imported keys:

rpm -q gpg-pubkey --qf "%{version} %{summary}\n"

Local RPM files are a separate case, and the two tools disagree. We stripped the signature from a cowsay RPM with rpmsign --delsign. sudo dnf install -y ./cowsay-*.rpm installed it with only a warning, because localpkg_gpgcheck is off:

Warning: skipped OpenPGP checks for 1 package from repository: @commandline

Plain rpm refused the same file. We added --nodeps only to get past a missing Perl module and reach the signature check, which rpm enforces because Fedora sets %_pkgverify_level to all:

sudo rpm -i --nodeps cowsay-*.rpm

rpm stopped at the signature and installed nothing:

	package cowsay-3.8.4-6.fc45.noarch does not verify: no signature

So a package passed to dnf as a file is trusted without a signature, which is worth knowing before you script installs of downloaded RPMs. The NOKEY and signature errors guide walks through both behaviours and how to sign your own builds.

Undo, roll back and replay transactions

Every transaction is recorded in the history database together with its command line, so start with the list:

dnf history list

These rows from the lab VM cover the ffmpeg work in the install section:

ID Command line                            Date and time       Action(s) Altered
23 dnf install -y kernel-core-7.2.11-300.f 2026-11-06 05:32:05 I               2
22 dnf swap -y ffmpeg-free ffmpeg --allowe 2026-11-06 05:31:43 E, I           16
21 dnf install -y ffmpeg-free              2026-11-06 05:31:12 I             282
20 dnf install -y https://mirrors.rpmfusio 2026-11-06 05:25:25 I               1

Transaction 22 is the ffmpeg swap. Undo it by ID, even with nine later transactions on top of it:

sudo dnf history undo -y 22

dnf works out the reverse of the swap on its own, removing the RPM Fusion build and putting the -free packages back:

Package             Arch   Version                             Repository          Size
Removing:
 ffmpeg             x86_64 0:9.0.2-3.fc45                      rpmfusion-free   2.6 MiB
Removing unused dependencies:
 ffmpeg-libs        x86_64 0:9.0.2-3.fc45                      rpmfusion-free  23.9 MiB
 libavdevice        x86_64 0:9.0.2-3.fc45                      rpmfusion-free 211.5 KiB
 ocl-icd            x86_64 0:2.3.5-1.fc45                      fedora         236.2 KiB
 vmaf-models        noarch 0:3.2.1-1.fc45                      fedora           4.2 MiB
 vvenc-libs         x86_64 0:1.14.0-1.fc45                     rpmfusion-free   3.3 MiB
 x264-libs          x86_64 0:0.165-10.20250608gitb35605ac.fc45 rpmfusion-free   1.9 MiB
 x265-libs          x86_64 0:4.3-1.fc45                        rpmfusion-free  16.9 MiB
Installing:
 ffmpeg-free        x86_64 0:9.0.2-3.fc45                      updates          2.6 MiB
Installing dependencies:
 libavcodec-free    x86_64 0:9.0.2-3.fc45                      updates         11.2 MiB
 libavdevice-free   x86_64 0:9.0.2-3.fc45                      updates        244.3 KiB
 libavfilter-free   x86_64 0:9.0.2-3.fc45                      updates          4.3 MiB
 libavformat-free   x86_64 0:9.0.2-3.fc45                      updates          2.9 MiB
 libavutil-free     x86_64 0:9.0.2-3.fc45                      updates        983.4 KiB
 libswresample-free x86_64 0:9.0.2-3.fc45                      updates        155.2 KiB
 libswscale-free    x86_64 0:9.0.2-3.fc45                      updates          1.1 MiB

Transaction Summary:
 Installing:         8 packages
 Removing:           8 packages

undo reverses a single transaction, while rollback reverses every transaction after the one you name. That difference is bigger than it sounds. We rolled back to 20 to get rid of the ffmpeg-free install, and dnf also reversed the kernel test, the starship and gh installs and everything else we’d done since:

sudo dnf history rollback -y 20

It removed 285 packages, and before the summary it printed a merge error for the kernel we’d installed and then removed:

Transaction merge error: 'Action 'Install' 'kernel-core-7.2.11-300.fc45.x86_64' cannot be merged because it is already installed in version 'kernel-core-7.2.15-300.fc45.x86_64' -> keeping the action from older transaction with 'kernel-core-7.2.11-300.fc45.x86_64'.'
Transaction merge error: 'Action 'Install' 'kernel-modules-core-7.2.11-300.fc45.x86_64' cannot be merged because it is already installed in version 'kernel-modules-core-7.2.15-300.fc45.x86_64' -> keeping the action from older transaction with 'kernel-modules-core-7.2.11-300.fc45.x86_64'.'

The rollback still completed, but it took away tools we still wanted, because a rollback is all or nothing over the whole range. Read dnf history list before you run one, and reach for undo when you only want one change gone. Every history command also accepts last in place of an ID, which saves a lookup when the change you regret is the one you just made. We installed fish again, then inspected and undid that install without looking up its ID, so the IDs in your own list don’t matter for this pair:

dnf history info last
sudo dnf history undo -y last

The info block lists every package with the reason it was installed, and the undo removed all four packages from that install:

dnf5 history info last and history undo last removing fish and its dependencies on Fedora 45

To repeat a set of installs on another machine, store the transaction instead of running it. --store resolves the transaction and downloads the packages, but installs nothing:

sudo dnf install -y --store=/tmp/devtools zsh fish
ls -R /tmp/devtools

The directory holds a transaction.json and every RPM the transaction needs:

/tmp/devtools:
packages
transaction.json

/tmp/devtools/packages:
fish-4.6.0-2.fc45.x86_64.rpm
man-pages-6.13-6.fc45.noarch.rpm
ncurses-term-6.6-3.fc45.noarch.rpm
pcre2-utf32-10.47-1.fc45.2.x86_64.rpm
zsh-5.9.2-3.fc45.x86_64.rpm

Copy the directory to the target and replay it. dnf installs from the stored RPMs and shows @stored_transaction(fedora) as the repository:

sudo dnf replay -y /tmp/devtools

The replay ended with a line worth noticing: “Warning: skipped OpenPGP checks for 5 packages from repository: @stored_transaction(fedora)”. Stored RPMs are treated like local files, so only replay directories you built yourself or got over a channel you trust. sudo dnf history store 36 saves a past transaction instead, but writes only transaction.json into a ./transaction directory (change it with -o), so the target must be able to download the packages. History covers packages only. Config files and data changed after the install stay changed, which is what Btrfs snapshots with Snapper are for.

Run updates offline on the next reboot

An offline transaction downloads and test-runs everything now, then applies it during the next boot with no desktop session or services in the way. Add --offline to any install, upgrade or remove:

sudo dnf upgrade -y --offline python3-dbus
sudo dnf offline status
sudo dnf offline reboot

offline reboot asks “Is this ok [y/N]” before it reboots, so answer yes or pass -y. After the reboot the status is clear, the package is upgraded, and offline log lists one entry per boot that carried an offline transaction:

dnf5 offline status, offline reboot and offline log after the transaction ran on Fedora 45

dnf offline status needs root even though it only reads, and without sudo it fails with a “requires superuser privileges” message. That trips up monitoring scripts that run as a normal user, so give them a sudo rule for that one command or check from a root timer. Drop a staged transaction with sudo dnf offline clean. dnf offline-upgrade download and dnf offline-distrosync download are compatibility shortcuts that stage a transaction the same way. Release upgrades use it too through dnf system-upgrade download --releasever=N, which the Fedora 44 to 45 upgrade guide runs end to end with rollback options.

Tune downloads and config drop-ins

Most tuning advice says to raise max_parallel_downloads from its default of 3. We measured it by downloading LibreOffice Writer with every dependency (480 packages, 557 MB in November; 495 packages, 563 MB in October) and clearing the cache before each run:

dnf download --resolve --destdir /tmp/dl --setopt=max_parallel_downloads=10 libreoffice-writer

Raising the limit made no consistent difference on our connection:

SettingNovember run (Fedora 45 GA)October runs (two each)
max_parallel_downloads=3 (default)62.0 s56.6 s, 47.3 s
max_parallel_downloads=1077.5 s48.1 s, 48.8 s
max_parallel_downloads=20Not rerun97.9 s, 49.5 s
max_parallel_downloads=10 plus max_downloads_per_mirror=10Not rerun40.5 s, 49.5 s

Two things cap the gain, and both showed up in our runs:

  1. The link was already close to full. In October a single curl download from a Fedora mirror pulled 8.9 MB/s, close to what dnf managed at the default.
  2. max_downloads_per_mirror also defaults to 3, so raising only the parallel count changes little when most packages come from one mirror.

The November pair even ran the opposite way from the usual advice, with 10 streams slower than 3, which tells you mirror speed moves these numbers more than either setting does. Raise both values on links fast enough that one stream can’t fill them, and leave the defaults alone otherwise. fastestmirror, which picks mirrors by connection latency rather than throughput, is off by default, and we leave it off for the same reason.

Put persistent settings in a drop-in instead of editing the stock files:

sudo vim /etc/dnf/libdnf5.conf.d/50-local.conf

This is the drop-in we used to test which file wins. Keep its installonly_limit line on servers, and keep the parallel line only on a link fast enough to use it, together with max_downloads_per_mirror, as the bench above showed:

[main]
max_parallel_downloads=10
installonly_limit=4

Confirm the merged result with --dump-main-config:

dnf --dump-main-config | grep -E "^(max_parallel|installonly_limit)"

Both values came from the drop-in:

installonly_limit = 4
max_parallel_downloads = 10

/etc/dnf/dnf.conf wins over the drop-ins. With the drop-in at 10, sudo dnf config-manager setopt max_parallel_downloads=5 wrote the option to dnf.conf and dnf used 5. sudo dnf config-manager unsetopt max_parallel_downloads took it back out, and the drop-in’s 10 applied again. Pick one place for each option so the two files do not fight over it. Two more defaults shape behaviour: best=False (from Fedora’s own drop-in) lets dnf install an older candidate when the newest one has broken dependencies, and keepcache is off, so dnf deletes downloaded RPMs after each successful transaction.

Map DNF4 commands to DNF5

Most DNF4 syntax still works through compatibility aliases, but these commands and files changed:

TaskDNF4DNF5
Add a repo filednf config-manager --add-repo URLdnf config-manager addrepo --from-repofile=URL
Enable or disable a repodnf config-manager --set-enabled IDdnf config-manager enable ID (writes an override)
Persist a repo optiondnf config-manager --save --setopt=ID.key=valdnf config-manager setopt ID.key=val
Revert a repo optionEdit the repo filednf config-manager unsetopt ID.key
List groupsdnf grouplistdnf group list
List environmentsdnf group list (environments print first)dnf environment list
Versionlock file/etc/dnf/plugins/versionlock.list/etc/dnf/versionlock.toml
Remove old kernelsdnf remove $(dnf repoquery --installonly --latest-limit=-2 -q)dnf remove --oldinstallonly --limit=2
Offline updatednf offline-upgrade download (plugin)dnf upgrade --offline, then dnf offline reboot
Save and replay a transactiondnf history store plus dnf history replaydnf install --store=DIR or dnf history store ID, then dnf replay DIR

The DNF4 Python module still exists as python3-dnf, and the Fedora 45 Cloud image even has version 4.24.0 installed, but dnf5 does not use it. New tooling should target the python3-libdnf5 bindings, which use a different API. On Rocky Linux and AlmaLinux, which still ship DNF4, the DNF guide for RHEL-based distributions covers the older syntax. If you want one book that covers both families, Christopher Negus’s Linux Bible (paid link) (11th edition, Wiley, late 2025) builds its examples on Fedora 41, the first release with dnf5 as the default, alongside RHEL 9 and Ubuntu 24.04.

Fix common dnf5 errors

Each of these came up on the lab VMs while we rebuilt this cheat sheet, and the first three are the messages readers search for most.

Cannot load libdnf plugin enabled from 00-systemd-inhibit.conf

Every dnf command stops with this message, including the reinstall that would fix it. The second line names the real problem: libdnf5 found the plugin’s config file in /etc/dnf/libdnf5-plugins/ but not the library it points to. We reproduced it by moving /usr/lib64/libdnf5/plugins/systemd-inhibit.so aside, and the fix is to switch the broken plugin off for one command and reinstall its package:

dnf5 Cannot load libdnf plugin enabled from 00-systemd-inhibit.conf error and the disable-plugin reinstall fix on Fedora 45

A plain sudo dnf reinstall -y libdnf5-plugin-systemd-inhibit failed with the same message, because dnf loads its plugins before it does anything else. The working order is:

  1. Run sudo dnf --disable-plugin=systemd-inhibit reinstall -y libdnf5-plugin-systemd-inhibit to put the library back.
  2. Run sudo dnf upgrade --assumeno and confirm it no longer prints the plugin error.
  3. If a different plugin is named, use its name from the name = line in the .conf file and the package from rpm -qf on that file.

A Fedora Discussion thread from September 2026 reports the same first line on Fedora 44. We did not reproduce whatever removed the library on those systems, so check the second line of your error: when it says the library can’t be found, the reinstall above is the fix.

Unknown argument --set-enabled for command config-manager

This is DNF4 syntax reaching dnf5, usually from an older guide or script. dnf5 prints Unknown argument "--set-enabled" for command "config-manager". Add "--help" for more information about the arguments. and does nothing, and --add-repo fails the same way. Use the subcommands from the DNF4 to DNF5 map: config-manager enable ID and config-manager addrepo --from-repofile=URL.

No match for argument: Development Tools

dnf5 5.4.7.0 did not accept the display name in group install, and plain install never treats a bare name as a group. Look up the ID with dnf group list and install development-tools, or @development-tools through plain install. Do not add --skip-unavailable as the message suggests, because it turns the failure into an install that quietly does nothing.

Usable URL not found

A repo URL returned 404 for every mirror. With Fedora’s skip_if_unavailable=True, dnf skips the repo, finishes and exits 0, so the only symptom is a later “No match for argument” when you install a package from that repo. Turn the warning into a hard failure to confirm which repo it is:

sudo dnf --repofrompath=old-vendor,https://download.docker.com/linux/fedora/99/x86_64/stable --setopt=old-vendor.skip_if_unavailable=False makecache
echo "exit code: $?"

With the skip disabled, dnf names the repo and the URL and exits 1. The block below leaves out the four “Status code: 404” lines dnf printed before it, one per download attempt:

Updating and loading repositories:
 old-vendor                             100% |   1.2 KiB/s |   1.5 KiB |  00m01s
>>> Usable URL not found
Failed to download metadata (baseurl: "https://download.docker.com/linux/fedora/99/x86_64/stable") for repository "old-vendor": Usable URL not found
exit code: 1

On a new Fedora release this usually means the vendor hasn’t built for it yet. Disable the repo with config-manager disable until it catches up, or point it at the previous release’s tree with config-manager setopt ID.baseurl=... if the packages don’t depend on newer libraries.

Waiting for a lock on the system repository

A second dnf process or GNOME Software holds the lock, and dnf5 names that process and waits instead of failing. We started a Cockpit install in the background and ran a remove four seconds later:

Waiting for a lock on the system repository. The following processes are currently accessing it:
1002	dnf install -y cockpit
Use the "--skip-file-locks" option to bypass the lock.

Let the other transaction finish first. --skip-file-locks exists for stale lock files left by a crashed process, not for running two transactions at once, because both would write to the same RPM database. Our second command simply waited until timeout killed it after 15 seconds, and the Cockpit install in the background finished normally.

Skipping packages with broken dependencies

A versionlock, an exclude or a pinned third-party package holds back a dependency, so dnf skips the packages that need it. In our October test, a lock on kbd made dnf list its subpackages under “Skipping packages with conflicts” instead, and both headings point at the same kind of hold. Check the locks and excludes first:

dnf versionlock list
dnf --dump-main-config | grep -E "^excludepkgs"

If neither explains it, disable third-party repos one at a time with --disable-repo=ID and repeat the upgrade with --assumeno until the skip goes away. That repo is the cause, usually because it ships a library at a version that Fedora’s newer package cannot use, so the fix is to wait for the vendor or remove their copy.

Canceled by the user during a key import

This is the GitHub CLI case from the key section: a repo offered more than one key and one prompt got a “no”. Answer yes to each key from that repo, or reinstall with -y. The failed attempt left nothing behind, and the expired-pgp-keys plugin removes the expired key on the next transaction.

No offline transaction is stored

sudo dnf offline reboot prints this and exits 1 when nothing is staged, for example after offline clean or after the transaction already ran on an earlier boot. Check sudo dnf offline log to see whether it ran, and stage it again with --offline if it didn’t.

If you’re setting up a new Fedora 45 machine rather than fixing one, the cheat sheet pairs best with the post-install checklist: it adds RPM Fusion, Flathub and the codecs, and every dnf command it runs is one of the rows above.

Keep reading

How to Upgrade Fedora 44 to Fedora 45 (GUI, CLI and Rollback) Fedora How to Upgrade Fedora 44 to Fedora 45 (GUI, CLI and Rollback) Enable RPM Fusion and Install Multimedia Codecs on Fedora 45 / 44 / 43 Fedora Enable RPM Fusion and Install Multimedia Codecs on Fedora 45 / 44 / 43 Harden Fedora 45 / 44 for Desktop and Server: Complete Guide Fedora Harden Fedora 45 / 44 for Desktop and Server: Complete Guide Switch Fedora From GRUB to systemd-boot With bootctl Fedora Switch Fedora From GRUB to systemd-boot With bootctl Things to Do After Installing Fedora 45 Workstation Fedora Things to Do After Installing Fedora 45 Workstation Download Media Contents from the Web withΒ You-Get Fedora Download Media Contents from the Web withΒ You-Get

Leave a Comment

Press ESC to close