Skip to content
Français
Account: not signed in, sync available My learning
Select theme
Français

Manage packages with DNF

By updated

Improve this page

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:

Terminal window
sudo dnf install nginx

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

Terminal window
rpm -q nginx
dnf list installed nginx

On AlmaLinux 10, the output confirms the version and the appstream repository:

nginx-1.26.3-6.el10_2.5.x86_64
Installed Packages
nginx.x86_64 2:1.26.3-6.el10_2.5 @appstream

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

Terminal window
sudo dnf remove nginx
rpm -q nginx

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

Terminal window
dnf search apache

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

Terminal window
dnf provides /usr/bin/python3

The 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 interpreter
Repo : @System
Matched from:
Filename : /usr/bin/python3

Once the package is identified, info shows its version, size, licence and repository before you install it:

Terminal window
dnf info nginx

Going 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.

Terminal window
dnf repoquery --whatrequires zlib
dnf repoquery --userinstalled

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

Terminal window
sudo dnf upgrade

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

Terminal window
sudo dnf --exclude='kernel*' upgrade

The 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.

Terminal window
dnf updateinfo summary

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

Terminal window
dnf updateinfo list security
sudo dnf upgrade --security

Some 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?

Terminal window
sudo dnf needs-restarting -r
echo "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/27943
exit code: 1

After 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: 0

Without -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.

Terminal window
dnf group list

On 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.

Terminal window
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
expect

Remember 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.

Terminal window
sudo dnf group install "Development Tools"
sudo dnf group install "Development Tools" --with-optional

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

Terminal window
dnf group list --installed

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

Terminal window
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.

Terminal window
dnf repolist

To also see the disabled repositories, add all. The status column tells you which are enabled or disabled:

Terminal window
dnf repolist all

The 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.

Terminal window
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:

Terminal window
sudo dnf config-manager --set-enabled crb
dnf repolist | grep crb

The expected output confirms the activation:

crb AlmaLinux 10 - CRB

To 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 repository
baseurl=http://repo.internal.lan/monitoring/el10/
enabled=1
gpgcheck=1
gpgkey=http://repo.internal.lan/monitoring/RPM-GPG-KEY-monitoring

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

Terminal window
dnf repolist | grep internal-monitoring

Finally, you can ignore a repository for a single command, without changing anything permanently. It is useful to force the origin of a package:

Terminal window
sudo dnf install nginx --disablerepo=epel

Plugins: 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 --help
It 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).

Terminal window
sudo dnf install dnf-plugins-core
rpm -q dnf-plugins-core

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

Terminal window
dnf download nginx
ls nginx-*.rpm

Configure 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=1
installonly_limit=3
clean_requirements_on_remove=True
best=True
skip_if_unavailable=False

Note 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=10
fastestmirror=True
keepcache=True

max_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.

Terminal window
sudo dnf install dnf-automatic
rpm -q dnf-automatic

The 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 = security
download_updates = yes
apply_updates = yes
reboot = never
[emitters]
emit_via = stdio

Here, 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:

Terminal window
sudo systemctl enable --now dnf-automatic.timer
systemctl list-timers dnf-automatic.timer

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

Terminal window
dnf history list

On 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 | 1

Before undoing anything, inspect the transaction concerned. The command shows the user, the return code and the exact list of modified packages:

Terminal window
dnf history info 4

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

Terminal window
sudo dnf history undo 4
rpm -q nginx

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

Terminal window
sudo dnf downgrade nginx
sudo dnf reinstall nginx

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

Terminal window
systemctl list-unit-files | grep -i dnf
dnf-automatic.service static -
dnf-makecache.service static -
dnf-makecache.timer enabled enabled

DNF writes to flat files, under /var/log/. Three files interest you, and they answer different questions:

FileContentWhen to read it
/var/log/dnf.logFull DNF run, dependency resolution, pluginsA command fails or behaves oddly
/var/log/dnf.rpm.logReal RPM actions: Installed, Upgraded, ErasedKnowing what really changed on disk
/var/log/dnf.librepo.logDownloads, mirrors, network errorsA 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:

Terminal window
sudo tail -f /var/log/dnf.log
sudo grep -iE 'error|fail' /var/log/dnf.log

The dnf.rpm.log log is the most readable for an audit: each line says what was installed, updated or removed, with the exact version.

Terminal window
sudo tail -5 /var/log/dnf.rpm.log
SUBDEBUG Upgrade: openssl-1:3.5.5-4.el9_8.x86_64
SUBDEBUG Upgraded: openssl-1:3.0.7-24.el9.x86_64

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

NeedDNF 4 (RHEL 9 and 10)DNF 5 (Fedora)
Enable a repositorydnf config-manager --set-enabled crbdnf config-manager enable crb
List security advisoriesdnf updateinfo list securitydnf advisory list --security
Main log/var/log/dnf.log + dnf.rpm.log/var/log/dnf5.log
download, needs-restarting pluginsdnf-plugins-core packageBuilt into the binary
--no-packages on a groupDoes not exist (error)Marks the group without installing
Automatic updatesdnf-automatic packagednf5-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:

Terminal window
sudo dnf install nginx --allowerasing

Finally, 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:

Terminal window
sudo dnf autoremove

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

Terminal window
sudo dnf clean all
sudo dnf makecache

Troubleshooting

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.

SymptomLikely causeSolution
No match for argument: <package> although the package existsStale metadata or disabled repositorysudo dnf clean all && sudo dnf makecache, then dnf repolist all
No such command: config-managerPlugin missingsudo dnf install dnf-plugins-core
Warning: Group Web Server does not existGroup missing on RHEL 9 and 10dnf group list to read the real names
journalctl -u dnf returns nothingNo dnf.service unit existsRead /var/log/dnf.log and /var/log/dnf.rpm.log
Dependency conflict on upgradeInconsistent versions across repositoriessudo dnf distro-sync, otherwise --allowerasing after reading the summary
GPG check FAILEDRepository key missing or package alteredImport the right key (gpgkey=); do not bypass with --nogpgcheck
A service still runs with the old libraryProcess not restarted after the updatednf 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:

Terminal window
sudo dnf distro-sync
dnf repoquery --duplicates

Hands-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

  • update and upgrade are identical on DNF; the --security option is the only one that really matters on a production server.
  • dnf group install does not install optional packages. It takes the mandatory and default ones; you need --with-optional for the rest (282 packages versus 321 for Development Tools).
  • The --no-packages option 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.service unit exists: the logs are in /var/log/dnf.log, /var/log/dnf.rpm.log and /var/log/dnf.librepo.log (or dnf5.log on Fedora).
  • dnf history undo <ID> undoes a failed transaction. It is your safety net: inspect first with dnf history info <ID>.
  • config-manager, needs-restarting and download are plugins shipped by dnf-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 module no 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

6 questions
6 min
70% to pass

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

Is this project useful to you?

The guides, courses, quizzes and labs stay free. If you use them regularly, you can contribute to their upkeep and development.

Become a supporting member (new tab) One-time support (new tab) Learn more

Follow the project on LinkedIn