You type dnf install on two RPM-based servers and expect the same behaviour, but the generation underneath is not always identical. DNF4 still powers most RHEL-compatible releases today. DNF5, built on libdnf5, is available on newer Fedora releases and may be adopted more broadly as individual vendors integrate and support it.
This guide focuses on what matters for daily administration and script migration: version detection, renamed flags, history and group syntax, plugin layout, and automation pitfalls. I ran the commands below on DNF 4.20.0 and DNF5 5.4.2.1. When you compare output from two different distributions, repository metadata and package versions can change the result — that is not always a DNF4-versus-DNF5 difference.
Tested on: DNF 4.20.0; DNF5 5.4.2.1.
dnf command to DNF5 starting with Fedora 41. Many current RHEL-compatible systems still invoke DNF4 by default, although some releases, including Rocky Linux 10, may make DNF5 available separately. Always verify with dnf --version, command -v dnf4, and command -v dnf5 rather than assuming from the distribution name.
DNF4 vs DNF5 at a Glance
DNF4 uses the older DNF and libdnf stack with Python-based plugins in dnf-plugins-core. DNF5 is a reworked implementation on libdnf5 with a cleaner C++ core and a different plugin layout — not a wholesale rename of every familiar subcommand.
| Area | DNF4 | DNF5 |
|---|---|---|
| Core stack | DNF / libdnf | libdnf5 / dnf5 |
| Basic install/remove commands | Supported | Mostly compatible |
| Download directory option | --downloaddir (recent builds also alias --destdir) |
--destdir only |
| Disable repositories | --disablerepo |
--disable-repo (--disablerepo retained as a compatibility alias) |
| Enable extra repository | --enablerepo |
--enable-repo (--enablerepo retained as a compatibility alias) |
| Transaction history | dnf history or dnf history list |
dnf history list; explicit subcommand required |
| Group list | dnf group list may include environments |
Use dnf environment list for environments |
| Script output | Mostly human-formatted tables | JSON available for several commands |
| Task | DNF4 | DNF5 |
|---|---|---|
| Install package | dnf install nginx |
dnf install nginx |
| Remove package | dnf remove nginx |
dnf remove nginx |
| Upgrade packages | dnf upgrade |
dnf upgrade |
| Search package | dnf search nginx |
dnf search nginx |
| List repositories | dnf repolist |
dnf repo list (repolist remains a compatibility alias) |
| Download package | dnf download nginx |
dnf download nginx |
| Select destination | --downloaddir DIR |
--destdir DIR |
| View history | dnf history |
dnf history list |
Do not treat “uses dnf” as proof of DNF4. On DNF5 systems, /usr/bin/dnf may be a symlink to dnf5.
Check Whether You Are Using DNF4 or DNF5
The command name alone is not enough. Start with the version string, then confirm which binaries and RPMs are installed.
If dnf --version shows 4.x
dnf --version4.20.0
Installed: dnf-0:4.20.0-22.el10_2.rocky.0.1.noarch at Mon 06 Jul 2026 03:27:42 PM GMT
Built : Rocky Linux Build System <[email protected]> at Tue 19 May 2026 02:06:23 PM GMT
Installed: rpm-0:4.19.1.1-23.el10.x86_64 at Mon 06 Jul 2026 03:27:40 PM GMTA leading 4.x means you are on DNF4. Check whether a separate dnf5 binary exists:
command -v dnf5(no output — dnf5 not in PATH)On this system /usr/bin/dnf points at the classic DNF4 entry point:
ls -la /usr/bin/dnflrwxrwxrwx. 1 root root 5 May 19 05:30 /usr/bin/dnf -> dnf-3rpm -q dnf dnf5dnf-4.20.0-22.el10_2.rocky.0.1.noarch
package dnf5 is not installedIf dnf --version shows 5.x
dnf --versiondnf5 version 5.4.2.1
dnf5 plugin API version 2.0
libdnf5 version 5.4.2.1
libdnf5 plugin API version 2.2Here dnf and dnf5 are the same tool:
ls -la /usr/bin/dnf /usr/bin/dnf5lrwxrwxrwx. 1 root root 4 May 6 00:00 /usr/bin/dnf -> dnf5
-rwxr-xr-x. 1 root root 1679560 May 6 00:00 /usr/bin/dnf5You may find only the dnf5 RPM installed:
rpm -q dnf dnf5package dnf is not installed
dnf5-5.4.2.1-1.fc44.x86_64On releases that package both during migration, compare dnf4 --version and dnf5 --version on the same host to isolate generation differences from repository metadata.
Command-Line and Option Differences
Install, remove, upgrade, search, repolist, and repoquery still feel familiar on both generations. The breakage usually appears in flags, help syntax, plugins, and command output.
Download and destination directory options
On DNF4, dnf download often accepts both directory flag names:
dnf download --help | grep -E 'destdir|downloaddir'--destdir DESTDIR, --downloaddir DESTDIRdnf download --resolve --alldeps --downloaddir=/tmp/dnf4-mig htop(30/30): tzdata-2026b-1.el10_2.noarch.rpm 513 kB/s | 497 kB 00:00On DNF5, help shows only --destdir:
dnf download --help | grep destdir--destdir=DESTDIR Set directory used for downloading packages to.Passing the DNF4 flag on DNF5 fails immediately:
dnf download --downloaddir=/tmp/dnf5-test2 htopUnknown argument "--downloaddir=/tmp/dnf5-test2" for command "download". Add "--help" for more information about the arguments.See download RPM packages with all dependencies for the full offline-download workflow.
Repository enable and disable flags
DNF4 documents --disablerepo and --enablerepo in global help:
dnf --help | grep -i disablerepo | head -1--disablerepo [repo] Temporarily disable active repositories for theDNF5 uses --disable-repo and --enable-repo as the canonical names. The older DNF4 spellings are currently retained as compatibility aliases, but scripts should migrate to the canonical DNF5 forms rather than depend on aliases remaining indefinitely:
dnf --help | grep -i disable-repo | head -1--disable-repo=REPO_ID,... Disable repositories. List option. Supports globs, can be specified multiple times.DNF5 also adds --from-repo for restricting the requested package to one repository while resolving dependencies from other enabled repos. DNF4 does not provide an exact equivalent; restricting --repo applies to the entire transaction.
Help syntax changes
DNF4 still supports --help-cmd on subcommands such as download:
dnf download --help-cmd 2>&1 | head -1usage: dnf download [-c [config file]] [-q] [-v] [--version]DNF5 rejects it — use --help instead:
dnf download --help-cmd 2>&1 | head -1Unknown argument "--help-cmd" for command "download". Add "--help" for more information about the arguments.Group and Environment Command Differences
Group IDs and display names come from each distribution’s Comps repository metadata. Fedora and Rocky Linux may expose different groups even when both use the same DNF generation, so do not treat cross-distribution output as proof of DNF4-versus-DNF5 behaviour.
Both generations accept group specifications by name or ID where your release supports them:
dnf group install "Development Tools"
dnf group install development-toolsWhen using the general install command, prefix the group with @:
dnf install @development-toolsThe @ prefix belongs on commands that accept both package and group specifications. dnf group install already expects a group name or ID — do not pass @development-tools to dnf group install.
| DNF4 | DNF5 |
|---|---|
dnf group list --ids |
dnf group list always shows IDs |
dnf group list may include environments |
Use dnf environment list for environments |
dnf groupinstall GROUP alias |
Use dnf group install GROUP |
dnf groupinfo GROUP alias |
Use dnf group info GROUP |
dnf group mark install GROUP |
Use dnf group install --no-packages GROUP |
On DNF5, environments are listed separately:
dnf environment listID Name Installed
custom-environment Fedora Custom Operating System no
workstation-product-environment Fedora Workstation noGroup IDs may still differ across Fedora, RHEL, Rocky Linux, and other distributions because each repository publishes its own Comps metadata. Discover the ID on the target system with dnf group list rather than hard-coding one value in scripts.
Plugin and config-manager Differences
Not every DNF5 feature is a plugin. Official documentation classifies download, history, system-upgrade, and offline workflows as core DNF5 commands, while config-manager, needs-restarting, and reposync are plugins.
| Feature | DNF4 | DNF5 |
|---|---|---|
| Download | dnf-plugins-core |
Core DNF5 command |
| History | Core DNF4 command | Core DNF5 command; subcommand required |
| Config manager | Usually dnf-plugins-core |
DNF5 plugin |
| Needs restarting | Usually dnf-plugins-core |
DNF5 plugin |
| Reposync | dnf-plugins-core |
DNF5 plugin |
| System upgrade | Separate plugin/package on supported distributions | Core DNF5 command |
| Offline transactions | Distribution/plugin-specific workflow | Core --offline and dnf offline workflow |
On a typical DNF4 install you will see dnf-plugins-core:
rpm -qa | grep -iE 'dnf-plugin|dnf5' | sortdnf-plugins-core-4.7.0-10.el10.noarch
python3-dnf-plugins-core-4.7.0-10.el10.noarchOn DNF5 the stack is packaged differently:
dnf5-5.4.2.1-1.fc44.x86_64
dnf5-plugins-5.4.2.1-1.fc44.x86_64
libdnf5-5.4.2.1-1.fc44.x86_64
libdnf5-cli-5.4.2.1-1.fc44.x86_64config-manager syntax changes
DNF4 often adds a repository with an option:
sudo dnf config-manager --add-repo https://example.com/example.repoDNF5 uses the addrepo subcommand:
sudo dnf config-manager addrepo --from-repofile=https://example.com/example.repoTo create a repository from a base URL with DNF5:
sudo dnf config-manager addrepo --id=example --set=baseurl=https://example.com/packages/DNF5 also uses explicit subcommands for persistent repository state:
sudo dnf config-manager disable example
sudo dnf config-manager enable exampleThese commands create repository overrides rather than directly editing the original .repo file.
Transaction History and System State
DNF4 and DNF5 both provide transaction history, but they use separate history and system-state storage. They share the RPM database, yet DNF4 transaction records are not copied into the DNF5 history database during migration.
On DNF4, the bare command defaults to listing transactions:
dnf history5 | install -y epel-release | 2026-07-06 22:35 | Install | 1 >E
4 | install -y libwebp | 2026-07-06 22:30 | Install | 1
3 | install -y tar gzip | 2026-07-06 22:02 | Install | 1
2 | -y install git | 2026-07-06 21:09 | Install | 65 <
1 | | 2026-07-06 20:56 | Install | 307 >EThe explicit form returns the same list:
dnf history listDNF5 requires a subcommand. A bare dnf history returns:
Missing command. Add "--help" for more information about the arguments.Use dnf history list instead:
dnf history listID Command line Date and time Action(s) Altered
2 dnf5 --config /kiwi_dnf5.config -y -- 2026-07-09 06:47:24 1
1 dnf5 --config /builddir/result/image/ 2026-07-09 06:47:17 147Other DNF5 history operations include:
dnf history info last
dnf history undo last
dnf history redo last
dnf history rollback TRANSACTION_IDA bare dnf history on DNF5 is a command-syntax difference, not missing history support.
During a supported migration, DNF5 can migrate system-state information such as package install reasons, but older DNF4 transaction history is not carried forward. Continued use of both tools can also make packages installed by one appear user-installed to the other. Do not manually merge history databases.
Migrate Scripts and Automation to DNF5
Review automation any time the underlying DNF generation changes on systems you manage.
Update renamed options
| DNF4 | DNF5 |
|---|---|
dnf download --downloaddir=DIR |
dnf download --destdir=DIR |
dnf --disablerepo='*' install … |
dnf --disable-repo='*' install … |
dnf repolist |
dnf repo list (repolist is retained as an alias) |
dnf download --help-cmd |
dnf download --help |
Update history and group commands
Replace bare dnf history with dnf history list on DNF5. Replace deprecated group aliases (groupinstall, groupinfo, grouplist) with dnf group install, dnf group info, and dnf group list. Query environments with dnf environment list instead of expecting them in dnf group list.
Update config-manager commands
Move from option-style dnf config-manager --add-repo … to subcommand-style dnf config-manager addrepo …, and use dnf config-manager enable / disable for persistent repository state. See the examples in the plugin section above.
Avoid parsing human-readable output
dnf repoquery --info still prints human-formatted text. DNF5 provides JSON for several commands — prefer that in scripts when it is available:
dnf list --json nginx{
"available_packages":[
{dnf history list --json[
{
"id":2,
"command_line":"dnf5 --config \/kiwi_dnf5.config -y --disable-plugin=priorities,versionlock --releasever=44 --setopt=clean_requirements_on_remove=true remove systemd-standalone-tmpfiles",
"start_time":1783579644,dnf history info last --json
dnf needs-restarting --jsonNot every DNF command offers JSON output, so scripts should also validate documented exit codes and avoid relying on column spacing, progress bars, or translated status messages. For example, dnf check-upgrade normally exits 100 when updates exist and 0 when none are available — do not treat 100 as a generic failure.
Ansible users should verify which package backend their managed hosts require. Current ansible-core provides both ansible.builtin.dnf and ansible.builtin.dnf5. The DNF5 module requires libdnf5 Python bindings, and some parameters may differ from the older DNF module, so test existing playbooks against the target ansible-core and distribution versions.
For a broader command reference, see the DNF command cheat sheet.
Should You Replace DNF4 with DNF5 Manually?
Generally no — use the package manager your distribution ships and supports.
| Situation | Recommendation |
|---|---|
| Distribution already on DNF5 | Use dnf as documented for that release |
| Distribution still on DNF4 | Stay on DNF4 until your vendor documents a supported migration |
| Pre-migration testing | Use a VM or container with the target generation, not unsupported package swaps on production |
| Both binaries installed | Possible during transitions, but not for routine mixed use |
A DNF5 package being available does not mean the distribution has adopted it as the supported default or recommends replacing DNF4. Installing DNF5 alongside DNF4 may be useful for testing when the vendor packages both, but follow the distribution's documented migration path before changing production package-management workflows.
Common Problems and Best Practices
| Symptom | Likely cause | Fix |
|---|---|---|
Unknown argument --downloaddir |
You are on DNF5 | Use --destdir |
Unknown argument --help-cmd |
DNF5 subcommand | Use --help |
Missing command for bare dnf history |
DNF5 requires a subcommand | Use dnf history list |
Module or Group '@…' is not available with dnf group install |
@ syntax used on the wrong command |
Use dnf install @group or dnf group install group-id |
No match for argument: Development Tools |
Group name is absent from the target's Comps metadata, or a name containing spaces was not quoted | Run dnf group list; use the listed ID or quote the exact display name |
| Script parses old DNF4 table output | DNF5 changed formatting | Use --json where available, or repoquery / rpm -q |
| Packages show odd install reasons after switching tools | Separate DNF4/DNF5 state | Use one supported tool per system |
dnf check-upgrade exits 100 |
Updates are available | Treat exit 100 as updates present, not script failure |
| Manually forcing DNF5 before vendor support | Unsupported or incomplete packaging | Follow vendor guidance; test in VMs or containers |
Run dnf --version in playbooks and documentation prerequisites. Pin flag names per generation in scripts. Retest group installs, offline installs, and download bundles after a DNF generation change.
Summary
Most day-to-day package operations still start with dnf install, dnf remove, and dnf upgrade. The practical migration work is in flags (--downloaddir vs --destdir, --disablerepo vs --disable-repo), history syntax (dnf history vs dnf history list), group and environment commands, config-manager subcommands, plugin layout, and separate transaction-history databases.
Before you update scripts or tutorials, run dnf --version on each system you manage, verify which plugins and JSON output options exist there, and exercise your workflows on the DNF generation your users actually run. Follow your distribution’s supported default rather than manually replacing the system package manager.

