dnf and the RHEL side
RHEL/Fedora uses the same two-layer model as Debian: dnf is the front-end, rpm is the low-level engine. Repository config lives in /etc/yum.repos.d/. Every apt command has a dnf parallel — learning the mapping makes you effective on both distro families.
You have spent three years on Ubuntu. Every server you have deployed uses apt. Then you join a team running RHEL 9 in production — and every command you type fails. apt is not found. sources.list does not exist. The package names are slightly different. The path to “just install nginx” is now three unfamiliar commands and a repository file you have never written. This lesson is the bridge. The architecture is identical — front-end over a low-level engine — and once you see the mapping, you will be functional on RHEL/Fedora/CentOS Stream within the hour.
After this lesson you can translate common apt commands to their dnf equivalents, manage repository files in /etc/yum.repos.d/, use rpm to query and verify installed packages, explain what AppStream modules are and why they replace some simple dnf installs, and explain the RHEL vs Fedora vs CentOS Stream relationship.
The two-layer model is identical across distro families. On Debian/Ubuntu: apt (front-end) → dpkg (engine). On RHEL/Fedora: dnf (front-end) → rpm (engine). The same separation of concerns applies — dnf resolves dependencies and manages repositories, rpm manages the local database and file installation.
| apt (Debian/Ubuntu) | dnf (RHEL/Fedora) | what it does |
|---|---|---|
apt update | dnf check-update | refresh package index |
apt upgrade | dnf upgrade | install available updates |
apt install nginx | dnf install nginx | install a package |
apt remove nginx | dnf remove nginx | remove a package |
apt autoremove | dnf autoremove | remove unused dependencies |
apt search nginx | dnf search nginx | search the index |
apt show nginx | dnf info nginx | show package metadata |
# RHEL/Fedora equivalents of common apt workflows
sudo dnf check-update # check for updates (does not install)
sudo dnf upgrade # install all available updates
sudo dnf install nginx # install nginx
sudo dnf remove nginx # remove nginx
dnf info nginx # show version, size, descriptionOne behavioural difference: dnf upgrade automatically runs the equivalent of apt update first — it refreshes the cache before checking for upgrades. You do not need to run dnf check-update separately before dnf upgrade.
Repository configuration lives in /etc/yum.repos.d/*.repo files. The format is INI, similar to systemd unit files:
# /etc/yum.repos.d/nginx.repo
[nginx-stable]
name=nginx stable repo
baseurl=http://nginx.org/packages/centos/$releasever/$basearch/
gpgcheck=1
enabled=1
gpgkey=https://nginx.org/keys/nginx_signing.key
module_hotfixes=trueKey fields: baseurl (or mirrorlist), gpgcheck=1 (verify signatures — always keep this 1), enabled=1/0 (toggle without deleting), gpgkey (URL or path to the GPG key).
# Add a repo from a .repo file
sudo dnf config-manager --add-repo https://example.com/package.repo
# List enabled repositories
dnf repolist
# List all repos including disabled
dnf repolist --all
# Enable/disable a repo by ID
sudo dnf config-manager --set-enabled nginx-stable
sudo dnf config-manager --set-disabled nginx-stableThe $releasever and $basearch variables are expanded by dnf at runtime — $releasever becomes 9 on RHEL 9, and $basearch becomes x86_64 or aarch64 depending on the system. This makes a single .repo file work across major versions.
rpm is the low-level engine — the dpkg equivalent. All installed package metadata lives in /var/lib/rpm/ (an SQLite database, unlike dpkg’s plain-text format). The query flags mirror dpkg’s:
| dpkg command | rpm equivalent | purpose |
|---|---|---|
dpkg -l | rpm -qa | list all installed packages |
dpkg -L nginx | rpm -ql nginx | list files in package |
dpkg -S /usr/sbin/nginx | rpm -qf /usr/sbin/nginx | find owning package |
dpkg -V nginx | rpm -V nginx | verify file checksums |
dpkg -i ./pkg.rpm | rpm -i ./pkg.rpm | install from local file |
# List all installed packages
rpm -qa
# Which package owns this file?
rpm -qf /usr/bin/nginx
# List all files installed by nginx
rpm -ql nginx
# Verify nginx files against the RPM database (detects tampering)
rpm -V nginx
# Output format: SM5DLUGTP c /etc/nginx/nginx.conf
# S=size, M=mode, 5=md5, D=device, L=symlink, U=user, G=group, T=mtime
# Install a local .rpm file (rpm has no dependency resolution — use dnf when possible)
sudo rpm -ivh ./package-1.0.x86_64.rpm
# -i=install, -v=verbose, -h=hash progress barsLike dpkg, rpm -i does no dependency resolution. If a .rpm requires libfoo, you must install libfoo first or use dnf localinstall ./package.rpm which calls rpm but also resolves dependencies from configured repos.
AppStream modules are a RHEL/CentOS Stream concept with no direct apt equivalent. They let you install multiple versions of the same software stream (e.g. nginx 1.20 stable vs 1.24 mainline) without adding a third-party repo. This is RHEL’s answer to Ubuntu’s PPAs.
# List available module streams for nginx
dnf module list nginx
# Name Stream Profiles Summary
# nginx mainline [d] common [d] A high performance web server
# nginx 1.22 common ...
# Enable a specific stream (locks the version stream)
sudo dnf module enable nginx:mainline
# Install from the enabled stream
sudo dnf module install nginx
# Check which stream is active
dnf module list nginx | grep '\[e\]' # [e] = enabled, [d] = default, [i] = installed
# Switch streams (requires a reset first)
sudo dnf module reset nginx
sudo dnf module enable nginx:1.22
sudo dnf module install nginxAppStream modules exist in /etc/dnf/modules.d/. When a stream is enabled, dnf will not automatically switch to a newer stream on dnf upgrade — stream switching is explicit, preventing surprise major-version upgrades.
The RHEL family tree: RHEL → CentOS Stream → Fedora. Understanding which you are on affects how you add repositories and what packages are available.
Fedora (cutting-edge, 6-month release cycle)
↓ features flow downstream after stabilization
CentOS Stream (rolling preview of next RHEL minor release)
↓ cherry-picked, tested, hardened
RHEL (Red Hat Enterprise Linux — paid support, 10-year lifecycle)
↓ binary-compatible rebuilds
AlmaLinux / Rocky Linux / Oracle Linux (free RHEL rebuilds)# Check which distro and version you are on
cat /etc/os-release
# ID=rhel / fedora / centos
# VERSION_ID=9.3
# RHEL requires a subscription to access base repos
subscription-manager register --username=user --password=pass
# On AlmaLinux/Rocky (RHEL rebuilds, no subscription needed)
dnf install epel-release # Extra Packages for Enterprise LinuxEPEL (Extra Packages for Enterprise Linux) is the RHEL equivalent of Ubuntu’s universe component — community-maintained packages not in RHEL’s official repos. If you cannot find a package via dnf search, add EPEL first.
Set up nginx from the official nginx repo on a fresh Rocky Linux 9 server.
Rocky Linux 9 is a RHEL 9 rebuild. The default AppStream nginx is older; the official nginx repo provides the latest stable.
# 1. Check what is currently available (AppStream version)
dnf module list nginx
# nginx mainline common [d] ...
# 2. Add the official nginx repository
sudo tee /etc/yum.repos.d/nginx.repo << 'EOF'
[nginx-stable]
name=nginx stable repo
baseurl=http://nginx.org/packages/centos/$releasever/$basearch/
gpgcheck=1
enabled=1
gpgkey=https://nginx.org/keys/nginx_signing.key
module_hotfixes=true
EOF
# 3. Import the GPG key explicitly
sudo rpm --import https://nginx.org/keys/nginx_signing.key
# 4. Check the priority — does the new repo win?
dnf info nginx
# Available Packages section should show the nginx.org version
# 5. Install (module_hotfixes=true allows the repo to override AppStream)
sudo dnf install nginx
# 6. Verify the installed version and file ownership
rpm -q nginx # nginx-1.26.x-1.el9.ngx.x86_64
rpm -qf /usr/sbin/nginx # nginx-1.26.x-...
rpm -V nginx # no output = all files intactThe module_hotfixes=true flag is RHEL-specific: without it, DNF’s modular filtering blocks non-AppStream packages from overriding AppStream packages, even when a higher-priority repo provides them.
▸Why this works
Why does RHEL use a SQLite database for the RPM database while dpkg uses plain text? The RPM database predates dpkg and was designed for atomic updates using Berkeley DB (now SQLite). Plain text is simpler to inspect and repair manually when corrupt — a corrupt dpkg status file can be fixed with a text editor in an emergency. A corrupt RPM database requires rpm --rebuilddb which re-generates it from the installed package headers. Both approaches have recovered from corruption in production at 3am; the dpkg recovery is slightly more transparent because you can read and edit the status file directly.
▸Common mistake
On RHEL/CentOS Stream, never disable gpgcheck in a .repo file to “just get it working.” This is the single most common mistake when troubleshooting repo GPG errors. The real fix is importing the GPG key: sudo rpm --import <gpg-key-url>. Disabling gpgcheck=0 silently opens the door to unsigned packages from that repository — any compromised mirror can now ship arbitrary binaries that rpm will install without complaint. If a key import fails, verify the URL is correct and the key file is ASCII-armored (starts with -----BEGIN PGP PUBLIC KEY BLOCK-----).
On a RHEL 9 server you want to find which package installed the binary at /usr/bin/python3.11. Which command gives you the answer?
The RHEL/Fedora package stack mirrors Debian exactly: dnf is the front-end (dependency resolution, repo management), rpm is the engine (local database, file installation). Repository config lives in /etc/yum.repos.d/*.repo — always keep gpgcheck=1. Key rpm queries: -qa (list all), -qf (owning package for a file), -ql (files in package), -V (verify checksums). AppStream modules provide multiple version streams without third-party repos. The RHEL family: Fedora (cutting-edge) → CentOS Stream (RHEL preview) → RHEL/AlmaLinux/Rocky. EPEL adds community packages the way Ubuntu’s universe does. The two-layer model is universal — learn it once, apply it everywhere.
Practice
Start at the top. Tasks go easiest → hardest: recall a fact, apply it to a case, then a senior-level stretch. Open one, attempt it, then reveal.
Something unclear?
Ask a question about this lesson. Questions are anonymous and go straight to the author to make the lesson better.