DNF is the package manager of Red Hat distributions and their derivatives (RHEL, AlmaLinux, Rocky Linux, Fedora). This page teaches you to install, update, search for and remove software with DNF, then to roll back when an update breaks a service. Every action is followed by its verification command: on a server, a command that "seems" to have worked is not enough.
You will also see where DNF really writes its logs (not in
journalctl), how to chain repositories, groups and plugins, and what
changes between DNF 4 and DNF 5.
What you will learn
- Install, check and remove a package, with the check that proves the result.
- Search for the package that provides a given file or command.
- Apply only the security updates without pulling in the rest.
- Install a group of packages while controlling what is really pulled in.
- Undo a failed transaction with the DNF history.
- Diagnose a failure by reading DNF's real log files.
Prerequisites
You need a machine running RHEL 10, AlmaLinux, Rocky Linux or
Fedora, and an account with sudo. Every command that
changes the system requires root privileges: they are prefixed with
sudo in this guide. Read-only commands (search, info,
repoquery) work without privileges.
If the notion of a package manager is new to you, the page Linux package managers sets the shared vocabulary of DNF, APT and Zypper.
Install, check, remove: the basic loop
This is the loop you will repeat every day. Install a package, then
check that it is really present in the RPM database, then remove it
cleanly. The check is not a luxury: DNF can print
Complete! while it actually installed a different version from the one
you aimed for, or replaced one package with another.
Install the nginx web server:
sudo dnf install nginxCheck the result right away. The first command queries the local RPM database, the second shows the source repository (right-hand column), which tells you where the package really comes from:
rpm -q nginxdnf list installed nginxOn AlmaLinux 10, the output confirms the version and the appstream repository:
nginx-1.26.3-6.el10_2.5.x86_64Installed Packagesnginx.x86_64 2:1.26.3-6.el10_2.5 @appstreamTo remove the package, use remove. DNF also removes the dependencies
that became orphans, because the clean_requirements_on_remove=True option is enabled
by default on RHEL. Then check that the RPM database no longer knows the package:
sudo dnf remove nginxrpm -q nginxThe expected output is package nginx is not installed. An rpm -q that still
returns a version means that the removal failed or that another package
reinstalls the dependency.
Search for a package before installing it
Half of the work is finding the right package name. Three commands
are enough, and they answer three different questions. Mixing them up wastes
time: search looks in names and descriptions, provides
looks in the package contents, info describes a package already identified.
When you know a keyword but not the exact package name:
dnf search apacheWhen you know a file or a command and look for the package that
ships it, provides is what you need. It is the command that saves you when a
script fails on a command not found:
dnf provides /usr/bin/python3The output names the package and the repository, here the local RPM database (@System):
python3-3.9.18-1.el9_3.x86_64 : Python 3.9 interpreterRepo : @SystemMatched from:Filename : /usr/bin/python3Once the package is identified, info shows its version, size,
licence and repository before you install it:
dnf info nginxGoing further, repoquery queries the repository metadata without
installing anything. Two uses come back in administration: knowing who depends
on a library before removing it, and telling what you explicitly installed
from what came in as a dependency.
dnf repoquery --whatrequires zlibdnf repoquery --userinstalledUpdate the system without breaking everything
On DNF, update and upgrade are two names for the same command. No
difference in behaviour, contrary to what your memory of APT might
suggest. Both update every package available:
sudo dnf upgradeOn a server, you often want to freeze the kernel or a business application
while the rest moves on. The --exclude option accepts a wildcard, provided
you protect it from the shell with single quotes:
sudo dnf --exclude='kernel*' upgradeThe most frequent case in production remains the security-only update.
Start with an inventory: updateinfo summary counts the pending security
advisories and sorts them by severity.
dnf updateinfo summaryOn a fresh AlmaLinux 10, the output shows the backlog at once:
Updates Information Summary: available 10 Security notice(s) 2 Important Security notice(s) 6 Moderate Security notice(s) 2 Low Security notice(s)Then list the detail of the advisories, and apply only those. The security fixes go through, the new features wait for the next maintenance window:
dnf updateinfo list securitysudo dnf upgrade --securitySome services keep running with the old library loaded
in memory after an update. The needs-restarting command tells you so, and
its -r option answers the only question that matters: must you reboot the
machine?
sudo dnf needs-restarting -recho "exit code: $?"This command has two outputs and two exit codes, and knowing only one of them sets you up for a bad surprise. Measured on AlmaLinux 10.2 with dnf 4.20.0, right after a kernel update:
Core libraries or services have been updated since boot-up: * kernel-core
Reboot is required to fully utilize these updates.More information: https://access.redhat.com/solutions/27943exit code: 1After a reboot, on the same machine, both the answer and the code change:
No core libraries or services have been updated since boot-up.Reboot should not be necessary.exit code: 0Without -r, the command lists the processes to restart instead of deciding
for the whole machine. Run it with sudo: without root privileges, it cannot
read /proc/<pid>/smaps and pours out one line per inaccessible
process, 33 on a fresh VM, while still exiting with 0.
Failed to read PID 1's smaps.Failed to read PID 603's smaps.Failed to read PID 643's smaps.Install a package group
A group is a coherent set of packages delivered under a single name, for instance a complete build toolchain. It is a real time saver, but it is also the most misunderstood feature of DNF, because not all the packages of a group are equal.
First list the groups really available on your system. Do not guess their name: DNF refuses any group it does not know.
dnf group listOn AlmaLinux 10, you get environment groups (Server,
Minimal Install, Workstation) and regular groups (Development Tools, Container Management, Headless Management, RPM Development Tools).
Before installing, inspect the content of the group. This is where the whole misunderstanding lies: DNF sorts packages into three categories.
dnf group info "Development Tools"The output explicitly separates mandatory packages (Mandatory), default packages (Default) and optional packages (Optional):
Group: Development Tools Description: A basic development environment. Mandatory Packages: autoconf automake gcc make ... Default Packages: git valgrind ... Optional Packages: cmake expectRemember the rule: dnf group install installs the mandatory and
default packages, but NOT the optional ones. To include them, you need
the --with-optional option.
sudo dnf group install "Development Tools"sudo dnf group install "Development Tools" --with-optionalThe gap is measurable. On Rocky Linux 9, the first command announces
Install 282 Packages and pulls in neither cmake nor expect; the second announces
Install 321 Packages and includes them. Then check that the group is indeed
marked as installed:
dnf group list --installedRemoving a group follows the same logic. It removes the group's packages, but keeps those still required by other software, which avoids breaking the system:
sudo dnf group remove "Development Tools"Manage repositories
A repository is a source of packages. A RHEL server knows a few of them
by default (baseos, appstream, extras), and you will add some for
third-party software. Always start by looking at what is enabled, because a
package that cannot be found comes nine times out of ten from a disabled repository.
dnf repolistTo also see the disabled repositories, add all. The status column tells
you which are enabled or disabled:
dnf repolist allThe CRB repository (CodeReady Builder), which holds the development libraries, is the classic example. Start by looking at its state: depending on the distribution, it may already be enabled.
dnf repolist all | grep -E '^crb 'On RHEL, it ships disabled and requires explicit enabling.
On AlmaLinux 10, it shows up enabled without you doing anything, which
makes the enabling command have no observable effect. It is the reflex to adopt
with any repository: read the state before acting, otherwise you cannot tell whether
the command did anything.
If it is indeed disabled, enable it with the config-manager plugin,
then check that it appears in the list of enabled repositories:
sudo dnf config-manager --set-enabled crbdnf repolist | grep crbsudo dnf config-manager enable crbdnf repolist | grep crbThe expected output confirms the activation:
crb AlmaLinux 10 - CRBTo add a third-party repository, create a .repo file in /etc/yum.repos.d/,
the directory where RHEL stores the definition of its package sources. The name
of the file hardly matters, the identifier between brackets is the one you
will handle afterwards.
[internal-monitoring]name=Internal monitoring repositorybaseurl=http://repo.internal.lan/monitoring/el10/enabled=1gpgcheck=1gpgkey=http://repo.internal.lan/monitoring/RPM-GPG-KEY-monitoringThe gpgcheck=1 directive is the most important line of the file: it
enforces the signature check of every package. A repository without
gpgcheck accepts any RPM modified in transit. Check that DNF sees
the new repository before using it:
dnf repolist | grep internal-monitoringFinally, you can ignore a repository for a single command, without changing anything permanently. It is useful to force the origin of a package:
sudo dnf install nginx --disablerepo=epelPlugins: what is not shipped by default
Several commands presented as "DNF" are not in DNF. They are
plugins, gathered in the dnf-plugins-core package. On a
minimal Rocky 9 image, this package is missing, and the commands fail with a
misleading message:
No such command: config-manager. Please use /usr/bin/dnf --helpIt could be a DNF plugin command, try: "dnf install 'dnf-command(config-manager)'"This message does not mean the command does not exist: it means the
plugin is not installed. The three commands concerned are
config-manager (repository management), needs-restarting (services to restart)
and download (download without installing).
sudo dnf install dnf-plugins-corerpm -q dnf-plugins-coreOn AlmaLinux 10, these plugins are already present through
python3-dnf-plugins-core: the commands work without installing anything. On
a minimal Rocky 9 or a container, you must add them. Test rather
than assume.
The download plugin fetches an RPM into the current directory without
installing it. It is the basis of deployments in isolated environments (air
gap), where the target machine has no access to the repositories:
dnf download nginxls nginx-*.rpmConfigure DNF
The /etc/dnf/dnf.conf file controls the global behaviour. On AlmaLinux
10, its default configuration fits in five lines, and it is already sound:
[main]gpgcheck=1installonly_limit=3clean_requirements_on_remove=Truebest=Trueskip_if_unavailable=FalseNote that best=True is already on by default on RHEL: DNF always looks
for the best version available. Passing --best on the command line is
therefore redundant on this family of distributions. Likewise,
installonly_limit=3 keeps the last three kernels, which guarantees you
a fallback kernel at boot.
Three settings are worth adding, because they are not enabled by
default. You can check their current value with
dnf config-manager --dump:
max_parallel_downloads=10fastestmirror=Truekeepcache=Truemax_parallel_downloads is 3 by default; 10 is a common value,
and it is the most visible gain on a massive update. DNF 4.20 accepts
higher values without the slightest warning (measured up to 50): do not take
that silence as a guarantee that the setting is applied as is.
fastestmirror (off by default) ranks mirrors by latency.
keepcache (off by default) keeps the downloaded RPMs, useful if you
need to replay an installation or feed a local cache.
Automate updates
On a fleet of servers, applying security fixes by hand does not scale.
dnf-automatic does this work through a systemd timer. Beware of a
frequent confusion: it is not part of dnf-plugins-core, it is a
separate package to install explicitly.
sudo dnf install dnf-automaticrpm -q dnf-automaticThe behaviour is set in /etc/dnf/automatic.conf. By default, RHEL 10
downloads updates but does not apply them (apply_updates = no)
and never reboots (reboot = never). It is a cautious choice that you must
change knowingly:
[commands]upgrade_type = securitydownload_updates = yesapply_updates = yesreboot = never
[emitters]emit_via = stdioHere, upgrade_type = security restricts the automation to security
fixes only, which is the most defensible setting on a production
server. Then enable the timer, and check that it is armed and
when it will fire:
sudo systemctl enable --now dnf-automatic.timersystemctl list-timers dnf-automatic.timerThe dnf-automatic.timer timer fires at 06:00 (OnCalendar=*-*-* 6:00)
with a random delay. Variants exist depending on the intent:
dnf-automatic-download.timer (download only),
dnf-automatic-install.timer (install) and dnf-automatic-notifyonly.timer
(notify without doing anything). On Fedora, the package is called
dnf5-plugin-automatic.
Roll back with the history
This is the feature that sets DNF apart from a mere installer, and the one that will save you one day. DNF records every transaction in a database and can replay it backwards. After an update that breaks a service, you are not doomed to reinstall the machine.
List the transactions. Each one carries an identifier (ID), the command
run, the date and the number of packages affected:
dnf history listOn AlmaLinux 10, the output looks like this:
ID | Command line | Date and time | Action(s) | Altered------------------------------------------------------------------------------- 4 | -y install nginx | 2026-07-12 08:30 | Install | 5 EE 3 | -y install dnf-plugins-c | 2026-07-12 08:30 | Install | 1Before undoing anything, inspect the transaction concerned. The command shows the user, the return code and the exact list of modified packages:
dnf history info 4Then undo it. undo reverses the transaction: what was installed is
removed, what was updated is downgraded to its previous version.
Then check the real state of the package:
sudo dnf history undo 4rpm -q nginxWhen a single version causes trouble, downgrade is enough and avoids touching the
rest of the transaction. Reinstalling, for its part, repairs a package whose
files were corrupted, without overwriting your customised configuration
files:
sudo dnf downgrade nginxsudo dnf reinstall nginxWhere DNF writes its logs
A major point of attention, and a very widespread error on the web: DNF does not log
to journalctl. There is no dnf.service unit. A command
like journalctl -u dnf therefore returns nothing at all, and wrongly makes you
believe that no event was recorded.
The proof takes one command. The only systemd units related to DNF concern the cache and the automation, never DNF itself:
systemctl list-unit-files | grep -i dnfdnf-automatic.service static -dnf-makecache.service static -dnf-makecache.timer enabled enabledDNF writes to flat files, under /var/log/. Three files interest
you, and they answer different questions:
| File | Content | When to read it |
|---|---|---|
/var/log/dnf.log | Full DNF run, dependency resolution, plugins | A command fails or behaves oddly |
/var/log/dnf.rpm.log | Real RPM actions: Installed, Upgraded, Erased | Knowing what really changed on disk |
/var/log/dnf.librepo.log | Downloads, mirrors, network errors | A repository is unreachable or slow |
To follow an installation live or understand a recent failure, open the
main log. On Fedora (DNF 5), the file is called
/var/log/dnf5.log and replaces the previous ones:
sudo tail -f /var/log/dnf.logsudo grep -iE 'error|fail' /var/log/dnf.logThe dnf.rpm.log log is the most readable for an audit: each line says
what was installed, updated or removed, with the exact version.
sudo tail -5 /var/log/dnf.rpm.logSUBDEBUG Upgrade: openssl-1:3.5.5-4.el9_8.x86_64SUBDEBUG Upgraded: openssl-1:3.0.7-24.el9.x86_64DNF 4 and DNF 5: what changes
A confusion circulates widely: RHEL 10 has not moved to DNF 5. On
AlmaLinux 10.2, dnf --version returns 4.20.0 and the real binary is
/usr/bin/dnf-3. It is Fedora that switched to DNF 5 (dnf5), with
rewritten options. Writing your scripts in DNF 4 syntax therefore remains the right choice
for the RHEL fleet.
Here are the differences that really break a script, all checked in a container:
| Need | DNF 4 (RHEL 9 and 10) | DNF 5 (Fedora) |
|---|---|---|
| Enable a repository | dnf config-manager --set-enabled crb | dnf config-manager enable crb |
| List security advisories | dnf updateinfo list security | dnf advisory list --security |
| Main log | /var/log/dnf.log + dnf.rpm.log | /var/log/dnf5.log |
download, needs-restarting plugins | dnf-plugins-core package | Built into the binary |
--no-packages on a group | Does not exist (error) | Marks the group without installing |
| Automatic updates | dnf-automatic package | dnf5-plugin-automatic package |
Another structural difference concerns modularity (the AppStream
modules, which let you pick the major version of a language). On
Rocky 9, dnf module list shows streams such as mariadb 10.11 or maven 3.8.
On AlmaLinux 10, the same command returns no module at all: modularity
was removed from RHEL 10. Stop building procedures around
dnf module enable.
Security and points of attention
The GPG signature is your only guarantee that an RPM was not modified between
the repository and your server. It is on by default (gpgcheck=1), and the
--nogpgcheck option disables it. Treat this option as a last resort
in the lab: a signature error signals either a misconfigured repository or
a compromised package, and both deserve an investigation, not a workaround.
The --allowerasing option is the second trap. It allows DNF to remove
packages to resolve a conflict, and it can uninstall a component a
service depends on. Always run the command without -y first, and read the
Removing section of the summary before confirming:
sudo dnf install nginx --allowerasingFinally, dnf autoremove removes orphan dependencies. The command reduces
the attack surface, but it is aggressive: on a system where packages
were installed by hand, it can remove libraries still used
by a binary outside RPM. Inspect the proposed list before confirming:
sudo dnf autoremoveCleaning the cache, on the other hand, is risk-free. It frees space and forces the metadata to reload, which solves most "package not found" cases when the package does exist:
sudo dnf clean allsudo dnf makecacheTroubleshooting
Here are the symptoms you will meet most often, and how to clear them. The common reflex for almost all of them: refresh the metadata before looking further.
| Symptom | Likely cause | Solution |
|---|---|---|
No match for argument: <package> although the package exists | Stale metadata or disabled repository | sudo dnf clean all && sudo dnf makecache, then dnf repolist all |
No such command: config-manager | Plugin missing | sudo dnf install dnf-plugins-core |
Warning: Group Web Server does not exist | Group missing on RHEL 9 and 10 | dnf group list to read the real names |
journalctl -u dnf returns nothing | No dnf.service unit exists | Read /var/log/dnf.log and /var/log/dnf.rpm.log |
Dependency conflict on upgrade | Inconsistent versions across repositories | sudo dnf distro-sync, otherwise --allowerasing after reading the summary |
GPG check FAILED | Repository key missing or package altered | Import the right key (gpgkey=); do not bypass with --nogpgcheck |
| A service still runs with the old library | Process not restarted after the update | dnf needs-restarting -r then restart the service concerned |
The dnf distro-sync command deserves a separate mention. It aligns all
the installed versions with what the repositories offer, even if it means downgrading
packages. It is the way out of a crisis after mixing third-party repositories:
sudo dnf distro-syncdnf repoquery --duplicatesHands-on practice
DNF commands are forgotten unless they go through your fingers. Two labs cover this guide: the first has you declare a repository in a .repo file with gpgcheck and validate it in dnf repolist; the second runs the install, query and remove loop on packages, with an RPM check at every step.
Key points
updateandupgradeare identical on DNF; the--securityoption is the only one that really matters on a production server.dnf group installdoes not install optional packages. It takes the mandatory and default ones; you need--with-optionalfor the rest (282 packages versus 321 forDevelopment Tools).- The
--no-packagesoption does not exist in DNF 4, and in DNF 5 it marks the group without installing the packages. It is not a "minimal" mode. - DNF does not log to systemd. No
dnf.serviceunit exists: the logs are in/var/log/dnf.log,/var/log/dnf.rpm.logand/var/log/dnf.librepo.log(ordnf5.logon Fedora). dnf history undo <ID>undoes a failed transaction. It is your safety net: inspect first withdnf history info <ID>.config-manager,needs-restartinganddownloadare plugins shipped bydnf-plugins-core, missing from a minimal installation.- RHEL 10 still uses DNF 4 (4.20). DNF 5 is the Fedora path, with a different syntax for repositories and security advisories.
- Modularity has disappeared from RHEL 10:
dnf moduleno longer returns anything, do not build procedures on it.
Knowledge check
Check that the essentials of this guide are acquired. The questions only cover what has just been explained here.
Knowledge check
Check what you have learned with this interactive quiz
How it works
- The timer starts when you click Start
- Multiple choice, true/false and short answer questions
- You can move between questions
- Detailed results are shown at the end
Starts the quiz and the timer
Review
(0/0)Score by domain
A domain groups several guides and gets several questions, so its score is more reliable than a per-topic score, which often rests on a single question.
Summary
Strengths
No topic passed at 80% on at least two questions yet.
Weak areas
No topic below the pass mark.
Recommended next
Hints before you try again?
Continue the course: Install applications with Flatpak and Flathub
A new full quiz with random questions
Work again only on the questions you missed