TL;DR
dnf5 is not the default on RHEL 10, Rocky Linux 10, or AlmaLinux 10 yet: they all still ship classic dnf (dnf4). Fedora made the jump already, dnf5 became the default dnf command starting with Fedora Linux 41. The RHEL family is clearly heading the same way (modularity is already deprecated in RHEL 10.1 and Rocky 10, and dnf5 is the stated reason), but there is no announced date for it to replace dnf4 as default there. Check what you are actually running before you read another word:
$ rpm -q dnf5 2>/dev/null; readlink -f /usr/bin/dnf
package dnf5 is not installed
/usr/bin/dnf-3
The readlink output above is the shape to expect, not a capture from a specific box (see the honesty note in the plate below). If it ends in dnf5, you are already on dnf5, most likely because you are on Fedora 41 or newer. If it ends in dnf-3 or dnf-4, you are on classic dnf, which is where most RHEL-family servers are today. Either way, the breaking changes below are worth knowing before dnf5 shows up on a box you did not choose to upgrade.
Why dnf5 matters even if your servers are still on dnf4
dnf5 is not a theming update, it is a full rewrite: the old dnf and libdnf stack was mostly Python, dnf5 is a C++ core (libdnf5) with a different plugin architecture entirely. Fedora finished the migration first: the Changes/SwitchToDnf5 proposal was accepted for Fedora Linux 41, and on that release the /usr/bin/dnf symlink stopped pointing at dnf4 and started pointing at dnf5, for both fresh installs and upgrades. If you manage Fedora workstations or Fedora-based CI images, you are very likely already dealing with this whether you noticed or not.
RHEL 10, Rocky Linux 10, and AlmaLinux 10 have not made that switch. As of RHEL 10.1, the default dnf command is still dnf4. What Red Hat did move on is modularity: Red Hat's own RHEL 10.1 announcement says modularity "has been deprecated in favour of simpler and versioned RPMs" and that dnf now issues deprecation warnings for it. Rocky Linux 10's release notes go further and name the reason: the dnf module command was phased out in Rocky 10 specifically because dnf5's unified API no longer needs the old modular architecture to manage multiple versions of a package. That is the RHEL family telling you, in its own release notes, which direction this is going. There is no confirmed date for dnf5 to become the RHEL-family default, so do not plan a cutover around one, but "not yet" is doing a lot of work in that sentence.
The command syntax that will bite scripts first
dnf5 removed the convenience of optional subcommands. In dnf4, a lot of commands worked with or without an explicit verb. In dnf5, the verb is mandatory, per the upstream dnf5 changes-from-dnf4 reference:
$ dnf history 42
Error: Unknown argument "42" for command "history"
See 'dnf5 history --help' for more information
$ dnf5 history info 42
Transaction ID : 42
Begin time : ...
Begin rpmdb : ...
...
dnf updateinfo works the same way: it is now dnf5 advisory summary, with --summary, --list, and --info becoming subcommands instead of flags. The group aliases go the same direction: groupinstall, groupinfo, grouplist, grouperase, groupremove, and groupupdate are all gone, replaced by dnf5 group install, dnf5 group info, and so on. If you have shell scripts or Ansible tasks that shell out to any of the old spellings, they will fail loudly on a dnf5 box, which is at least honest: dnf5 does not silently ignore unknown syntax the way some tools do.
A quieter but more dangerous change: dnf5 fails the whole transaction by default when any argument on the command line does not match an installed or available package. dnf4 would just skip what it could not find and move on. If you have an install script that lists ten packages and shrugs if one is missing on older or newer point releases, that script now aborts instead. The escape hatch is the --skip-unavailable flag, but you have to add it on purpose, it is not the default anymore.
A short list of other flags that quietly vanished or changed meaning: -4 and -6 are gone, use the ip_resolve config option instead. --downloaddir is gone, use --destdir. -e/--errorlevel and -R/--randomwait are removed outright. --rpmverbosity moved to a config option. --sec-severity was renamed --advisory-severities. None of these are exotic, they show up in real cron jobs and patch scripts.
Modularity is gone, and it takes "dnf module" with it
If any of your provisioning scripts do something like dnf module enable nodejs:20 followed by an install, that pattern does not exist in the RHEL 10 generation regardless of whether you are on dnf4 or dnf5, because modularity itself was dropped from the content, not just from the tool. The replacement is simpler once you unlearn the old habit: query available versions directly, then install the exact version you want.
$ dnf repoquery --available nodejs
nodejs-1:<version-a>.el10.x86_64
nodejs-1:<version-b>.el10.x86_64
$ dnf install nodejs-<version-b>
Installed:
nodejs-1:<version-b>.el10.x86_64
The exact NEVRAs above are placeholders on purpose, they move with every repo sync and I am not going to print a specific build number as if I captured it live when I did not. The pattern is what matters: repoquery to see what is actually available, then install the exact NEVRA you want. No module enable, no stream to track, no module-vs-package conflict to debug at 2am. It is a genuine simplification once you rewire your muscle memory, but it is also a breaking change for anything written against the old module workflow.
Config and cache paths that automation quietly depends on
A few defaults changed in ways that will not error, they will just behave differently, which is worse for debugging. The best option (prefer the newest available package version) now defaults to true; it defaulted to false in dnf4. cacheonly went from a boolean to a three-way enum (all, metadata, none), so a script that sets cacheonly=1 in a config file needs to become cacheonly=all. The cache directory itself moved: dnf5's system cache lives at /var/cache/libdnf5 instead of /var/cache/dnf, and the per-user cache moved to ~/.cache/libdnf5. If you have monitoring or disk-cleanup jobs hardcoded to the old paths, they will keep running without error and quietly stop doing anything useful.
Two options that fully disappeared rather than moved: retries and deltarpm/deltarpm_percentage are not supported in dnf5 at all, deltarpm support is not currently planned. And metadata_timer_sync, the setting that used to control background metadata refresh timing, is obsoleted by the dnf5-makecache.timer systemd unit instead. If your config management still writes the old key into /etc/dnf/dnf.conf, it is a no-op on a dnf5 box, not an error, which is exactly the kind of thing that only surfaces when a patch window quietly does not do what you expected.
Plugins, PackageKit, and Ansible are the rough edges
The biggest real-world friction is not the CLI, it is everything built on top of it. dnf4's plugin ecosystem was Python and lived in dnf-plugins-core; none of those plugins run unmodified under dnf5's C++ plugin API, they have to be ported. dnf-automatic became a dnf5 plugin rather than a standalone package, and during the early Fedora 41 rollout it hit a real, reported conflict: the plugin needs the full libcurl package, but that conflicts with the libcurl-minimal most systems ship by default, so installation failed outright (RHBZ #2322918). The documented workaround is to swap the minimal package for the full one first, dnf swap libcurl-minimal libcurl, before installing dnf-automatic. That specific bug is closed and fixed upstream now, but it is a good example of the kind of dependency friction a package's move from dnf4 to a dnf5 plugin can cause, and worth a test run before you trust it for unattended patching. Python plugin loading inside dnf5 itself had its own churn too, tracked upstream in the dnf5 project (issue #2648): the loader was reported broken on one Fedora release, and a specific follow-up case (a plugin importing a Python module after dnf5 had already loaded it once via dlopen) took a second fix, a change to the dlopen flags, before the tracker was closed. If you lean on a custom dnf plugin for compliance or inventory, verify it against the dnf5 version you actually have rather than assuming it "just works" because dnf4 plugins usually did, this class of bug has a real history here even after the specific reports get fixed.
Ansible noticed the same gap and did not try to paper over it: rather than silently pointing ansible.builtin.dnf at whichever backend happens to be installed, the project shipped a separate ansible.builtin.dnf5 module with the same core install/remove/group operations, talking to the dnf5 backend specifically. That split exists because the two backends genuinely do not behave identically from Ansible's point of view, the two modules had their own separate bug queues during the dnf5 rollout (one now-fixed example, RHBZ #2399781, had the classic dnf module breaking when libdnf5-plugin-notify-PackageKit was installed alongside it). If your playbooks assume one dnf module works everywhere, check which backend the target actually runs before you promote a playbook from a Fedora test box to a RHEL-family fleet, or the other way around.
How to try dnf5 without touching anything that matters
You do not need to gamble a production server to get a feel for dnf5 today, but do not expect it to be a plain dnf install dnf5 either. On the RHEL 10 family, dnf5 is not in BaseOS or AppStream, it ships through EPEL, and EPEL itself needs the CRB (CodeReady Builder) repository enabled first or its dependency chain breaks. On Rocky Linux 10 and AlmaLinux 10, the crb helper below (it ships inside the epel-release package) does that in one line; on subscription-managed RHEL proper, use subscription-manager repos --enable codeready-builder-for-rhel-10-$(arch)-rpms instead of the crb enable line:
$ sudo dnf install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-10.noarch.rpm
Installed:
epel-release-<version-a>.noarch
$ sudo crb enable
Enabled repositories: crb
$ sudo dnf install dnf5
Installed:
dnf5-<version-a>.x86_64
libdnf5-<version-a>.x86_64
$ dnf5 --version
dnf5 version: <dnf5-version>
libdnf5 version: <libdnf5-version>
$ dnf5 repoquery --installed | head -3
bash-0:<version-a>-<release>.el10.x86_64
coreutils-<version-b>-<release>.el10.x86_64
...
Calling the binary as dnf5 explicitly leaves /usr/bin/dnf pointed at classic dnf, so nothing about your existing scripts, cron jobs, or Ansible playbooks changes underneath them. That is the safest way to build the muscle memory for the new subcommands before the day dnf5 becomes the default here too.
Disclosure: the Vultr link below is a referral. You get 300 dollars of trial credit, usable for 30 days. This site gets a commission only if you stay active past 30 days and spend 100 dollars or more. That is the whole arrangement, both sides.
If you would rather not do any of this on a box that runs something real, this is exactly the kind of experiment a throwaway VPS is for. I use Vultr's 300 dollar trial credit for this: spin up the smallest Rocky Linux 10 or AlmaLinux 10 instance, install dnf5 side by side with dnf4, break your automation against it on purpose, then destroy the instance when you are done. It costs nothing but the time, and it means the first time your scripts meet dnf5 is not in production.
If this fails
No match for argument: dnf5when you try to install it: EPEL is not enabled, dnf5 is not in BaseOS or AppStream on the RHEL 10 family. Installepel-releaseand runsudo crb enablefirst, then retry.- An Ansible playbook that worked on a Fedora 41+ control node fails against a RHEL 10 target with a strange dnf-related error, or vice versa: the two ends disagree on which backend is in play. Pin the task to
ansible.builtin.dnf5oransible.builtin.dnfexplicitly instead of relying on Ansible's default detection. - A script that used to skip missing packages now aborts the whole transaction on dnf5: it hit the new default of failing on unmatched arguments. Add
--skip-unavailableif skipping was actually the intended behavior, do not just wrap the whole call in|| trueand lose the real errors too.
Wrap up
Nothing here is urgent if you run RHEL 10, Rocky Linux 10, or AlmaLinux 10 today, dnf4 is still the default and there is no announced date for that to change. But modularity is already gone from RHEL 10 content and Rocky's own release notes name dnf5 as the reason, which is about as clear a signal as a distro maintainer gives before a default flips. If you already run Fedora 41 or newer anywhere in your fleet, you are living with dnf5 right now, whether your scripts know it or not. Either way, the honest move is to install dnf5 next to classic dnf on a disposable box, run your actual playbooks and cron jobs against it, and find out what breaks before a distro upgrade finds out for you. If you are also setting up a fresh AlmaLinux or Rocky 10 box for this kind of testing, the initial server setup guide and the Rocky vs AlmaLinux breakdown cover the ground floor.