You're migrating from Rocky Linux to Ubuntu LTS or consolidating a fleet of mixed Fedora and Debian servers. The OS installation is straightforward; the real challenge is translating 300 installed packages, a dozen custom repositories, and two years of automation scripts that all assume DNF. When you migrate from DNF to APT, there's no one-click converter that preserves your package inventory, configuration management hooks, and deployment scripts. You need a methodical workflow that maps package names, translates repository configs, and converts automation without breaking production.
DNF and APT solve the same dependency resolution and package management problems with different tooling, metadata formats, and naming conventions. The core issue is not the package manager commands; those translate easily. It is the package names themselves: httpd becomes apache2, cronie becomes cron, and some RHEL-specific packages have no Debian equivalent at all. This guide walks through inventorying your DNF-based system, mapping packages to APT equivalents, and converting your automation so the migration does not break existing workflows.
Why DNF and APT Differ and What That Means for Migration
DNF uses RPM packages built from .spec files; APT uses DEB packages built from debian/control metadata. An RPM contains files, scripts, and dependency declarations in a cpio archive with an RPM header; a DEB is an ar archive with control metadata and data tarballs. Dependency resolution differs under the hood: DNF uses libsolv (a SAT solver), while APT uses its own resolver that prioritizes package versions and pinning. Repository metadata is incompatible: DNF reads repomd.xml and sqlite databases, APT reads Packages.gz and Release files. You cannot point DNF at a Debian repo or vice versa.
The core package manager commands translate directly, but the syntax and behavior differ in details:
| Task | DNF Command | APT Command |
|---|---|---|
| Install a package | dnf install httpd |
apt install apache2 |
| Search for a package | dnf search keyword |
apt search keyword |
| List installed packages | dnf list installed |
apt list --installed |
| Show package info | dnf info package |
apt show package |
| View transaction history | dnf history |
cat /var/log/apt/history.log |
| Clean cache | dnf clean all |
apt clean |
Package names rarely match between distributions. RHEL and Fedora use httpd for Apache, Debian and Ubuntu use apache2. Fedora's cronie is Debian's cron. Some packages split: a single RPM might become three DEBs (httpd becomes apache2, apache2-utils, and apache2-doc), or merge the other way. Migrations happen for vendor lock-in reduction, better upstream support for a given distro, or infrastructure consolidation after acquisitions. The package name mapping is the hardest part.
Inventorying Your DNF-Based System
Listing Installed Packages and Their Sources
Start by exporting every installed package with its source repository. This tells you which packages came from base repos, EPEL, or third-party sources:
dnf repoquery --installed --qf '%{name} %{reponame}' > installed-packages.txt
A typical Rocky Linux 9 system produces output like:
bash @System
systemd @System
httpd epel
nginx nginx-stable
postgresql15-server pgdg15
Packages marked @System were installed before repo metadata was available (often during initial install). Packages from epel, pgdg15, or nginx-stable need custom repo mapping. For a simple name-only list with versions:
rpm -qa --qf '%{NAME} %{VERSION}\n' > package-versions.txt
Check the DNF history to see what was explicitly installed versus pulled in as dependencies:
dnf history userinstalled > user-installed.txt
This lists only packages you or a script installed directly. Dependencies are omitted, so your APT migration can focus on the packages that matter.
Extracting Custom Repositories and Third-Party RPMs
List enabled repositories with full URLs:
dnf repolist -v | grep -E '(Repo-id|Repo-baseurl)'
For each third-party repo (EPEL, PostgreSQL, Nginx, Docker), note the URL and GPG key. You'll need to find the Debian equivalent: EPEL has no direct counterpart, but many EPEL packages exist in Debian's main or backports repos. PostgreSQL, Nginx, and Docker all publish official Debian/Ubuntu repos at the same upstream sources.
If you installed RPMs directly with rpm -i or dnf install ./package.rpm, find them:
dnf repoquery --installed --qf '%{name} %{vendor}' | grep -v 'Rocky\|Fedora\|Red Hat'
Packages from third-party vendors need manual conversion or sourcing from a Debian repo.
Documenting Package Groups and Weak Dependencies
DNF groups bundle related packages. List installed groups:
dnf group list --installed
Common groups like "Development Tools" or "System Tools" have rough Debian equivalents in metapackages like build-essential or task selections during install. Expand a group to see its contents:
dnf group info "Development Tools"
DNF supports weak dependencies (Recommends and Suggests). List them for a given package:
dnf repoquery --recommends httpd
APT also supports Recommends (installed by default) and Suggests (not installed). Check the Debian package management reference for how APT handles these. If you disabled weak deps on DNF with install_weak_deps=False in /etc/dnf/dnf.conf, replicate that in APT with APT::Install-Recommends "false"; in /etc/apt/apt.conf.d/.
Mapping DNF Package Names to APT Equivalents
Common Name Differences Across Distros
Most frustration comes from package name mismatches. Here are the frequent ones you'll hit:
| Fedora/RHEL Package (DNF) | Debian/Ubuntu Package (APT) | Notes |
|---|---|---|
httpd |
apache2 |
Apache web server |
httpd-tools |
apache2-utils |
Utilities like ab, htpasswd |
mariadb-server |
mariadb-server |
Same name |
NetworkManager |
network-manager |
Lowercase, hyphenated |
cronie |
cron |
Cron daemon |
policycoreutils |
none | SELinux tools; not needed on Debian (uses AppArmor) |
firewalld |
ufw or nftables |
Debian prefers ufw or direct nftables |
bind |
bind9 |
DNS server |
mod_ssl |
apache2 (built-in module) |
Enable with a2enmod ssl |
For packages specific to RHEL ecosystems (SELinux policy packages, redhat-lsb-core, or subscription-manager) there is no Debian equivalent. Remove them from your migration list or replace their functionality (SELinux with AppArmor, subscription-manager has no counterpart).
Using Package Search and Content Lookup
When a package name doesn't translate obviously, search by the binary or library it provides. On DNF:
dnf provides /usr/bin/htpasswd
Output: httpd-tools-2.4.57-5.el9.x86_64. On Debian, install apt-file first:
apt install apt-file
apt-file update
apt-file search /usr/bin/htpasswd
Output: apache2-utils: /usr/bin/htpasswd. For libraries, the same works:
apt-file search libssl.so.3
If you don't have a Debian system running yet, use packages.debian.org (search by file path in the "Search the contents of packages" box) or pkgs.org (cross-distro package search). These show package contents, dependencies, and available versions across Debian and Ubuntu releases.
Handling Split and Merged Packages
RHEL often bundles utilities into a single RPM; Debian splits them. The httpd RPM includes the server binary, apachectl, and utilities like htpasswd. On Debian, apache2 is the server, apache2-utils has the utilities, and apache2-doc has documentation. Your migration script must install all three if your automation depends on htpasswd.
Conversely, some Debian packages merge what RHEL splits. RHEL's python3 and python3-pip are separate; Debian's python3 often recommends python3-pip, installing it by default unless you disabled recommends. Check with:
apt show python3 | grep Recommends
For each package in your inventory, verify the mapping on a test Debian system with apt search and apt show, then document split/merge cases in a mapping file for your automation scripts to consume.
Translating Custom Repositories
From .repo Files to sources.list Entries
DNF repositories live in /etc/yum.repos.d/*.repo files with INI-style syntax. APT uses /etc/apt/sources.list and /etc/apt/sources.list.d/*.list with a one-line-per-repo format. A typical DNF repo file looks like:
[nginx-stable]
name=nginx stable repo
baseurl=https://nginx.org/packages/centos/$releasever/$basearch/
gpgcheck=1
enabled=1
gpgkey=https://nginx.org/keys/nginx_signing.key
The equivalent APT entry in /etc/apt/sources.list.d/nginx.list:
deb [arch=amd64 signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] https://nginx.org/packages/ubuntu/ jammy nginx
Key differences: DNF's baseurl becomes APT's deb line with URI, distribution codename (jammy for Ubuntu 22.04, bookworm for Debian 12), and component (nginx). The $releasever and $basearch variables don't exist in APT; you hardcode the distro name and architecture. The gpgkey URL must be downloaded and placed in /usr/share/keyrings/, then referenced with signed-by. Download and convert the key:
curl -fsSL https://nginx.org/keys/nginx_signing.key | gpg --dearmor -o /usr/share/keyrings/nginx-archive-keyring.gpg
List all DNF repo URLs to map them:
dnf config-manager --dump | grep -E '^(name|baseurl|gpgkey)'
For each third-party repo, check if the vendor publishes a Debian version. Docker, PostgreSQL, Nginx, and Node.js all maintain both RPM and DEB repos at the same upstream domain. Others don't: you'll need to find Debian alternatives or build from source.
Third-Party Repos: EPEL, RPM Fusion, and Their Debian Analogs
EPEL (Extra Packages for Enterprise Linux) has no direct Debian equivalent. Many EPEL packages already exist in Debian's main, contrib, or non-free repositories, or in Ubuntu's universe and multiverse. For example, EPEL's htop, nginx, and certbot are all in Debian main. Check with:
apt search htop
apt policy htop
If a package isn't in the default repos, Debian backports (deb http://deb.debian.org/debian bookworm-backports main) often has newer versions of software from testing. Ubuntu users can add PPAs (Personal Package Archives) with add-apt-repository, though PPAs are less vetted than official repos. For a PPA:
add-apt-repository ppa:ondrej/php
apt update
RPM Fusion (multimedia codecs, proprietary drivers) maps loosely to Debian's non-free and contrib repos, which you enable by editing /etc/apt/sources.list to include main contrib non-free non-free-firmware. Ubuntu's multiverse serves a similar role. The PostgreSQL Global Development Group (PGDG) maintains both RPM and DEB repos; convert Rocky's pgdg15 repo to Debian by following the official PostgreSQL APT instructions. Docker's official repo on RHEL (download.docker.com/linux/centos/) has a Debian counterpart at download.docker.com/linux/debian/ or download.docker.com/linux/ubuntu/. Add it with:
curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian bookworm stable" > /etc/apt/sources.list.d/docker.list
apt update
Document every custom repo in your migration plan. If a repo has no Debian equivalent, decide whether to build from source, use a Debian alternative, or skip the package entirely.
Converting Automation Scripts and Configuration Management
Shell Scripts and dnf Commands
A typical deployment script on Rocky Linux:
#!/bin/bash
dnf install -y httpd mod_ssl
systemctl enable --now httpd
firewall-cmd --add-service=http --permanent
firewall-cmd --reload
The Debian equivalent:
#!/bin/bash
apt-get install -y apache2
systemctl enable --now apache2
ufw allow 'Apache Full'
The -y flag (assume yes) works the same. DNF's --allowerasing (allow removing conflicting packages) becomes --allow-downgrades or --allow-remove-essential in APT, though you rarely need these. For non-interactive installs, prefix with DEBIAN_FRONTEND=noninteractive to suppress prompts:
DEBIAN_FRONTEND=noninteractive apt-get install -y apache2
Multi-package installs translate directly, but remember package name changes: dnf install -y httpd mariadb-server php php-mysqlnd becomes apt-get install -y apache2 mariadb-server php php-mysql. Keep a mapping file (package-map.txt) with one line per package: httpd=apache2, php-mysqlnd=php-mysql. Your script can read this file and substitute names before calling apt-get.
Ansible Playbooks: dnf vs apt Module
An Ansible task using the dnf module:
- name: Install web server
dnf:
name:
- httpd
- mod_ssl
state: present
update_cache: yes
The apt module equivalent:
- name: Install web server
apt:
name:
- apache2
state: present
update_cache: yes
Both modules support state: present, state: latest, and update_cache (equivalent to dnf makecache or apt update). The apt module has cache_valid_time to avoid updating the cache on every run:
- name: Install web server
apt:
name: apache2
state: present
update_cache: yes
cache_valid_time: 3600
This updates the cache only if it's older than 3600 seconds. For package removal, both use state: absent. The Ansible package module is distribution-agnostic, but it requires identical package names across distros, so it won't help with httpd vs apache2. Stick with dnf and apt modules and maintain separate playbooks or use variables:
vars:
web_server_package: "{{ 'httpd' if ansible_os_family == 'RedHat' else 'apache2' }}"
tasks:
- name: Install web server
package:
name: "{{ web_server_package }}"
state: present
Handling Systemd Units and Config File Paths
Systemd unit files are usually identical across distros, but the unit names differ. RHEL's Apache is httpd.service, Debian's is apache2.service. Your scripts must change:
systemctl enable --now httpd
to:
systemctl enable --now apache2
Configuration file paths diverge significantly. Apache on RHEL uses /etc/httpd/conf/httpd.conf and /etc/httpd/conf.d/*.conf; Debian uses /etc/apache2/apache2.conf, /etc/apache2/sites-available/, and /etc/apache2/mods-available/. Network configuration differs: RHEL's /etc/sysconfig/network-scripts/ifcfg-eth0 becomes Netplan YAML in /etc/netplan/*.yaml on Ubuntu 18.04+ or /etc/network/interfaces on older Debian. Build a checklist of common paths:
| Service | RHEL/Fedora Path | Debian/Ubuntu Path |
|---|---|---|
| Apache config | /etc/httpd/conf/httpd.conf |
/etc/apache2/apache2.conf |
| Nginx config | /etc/nginx/nginx.conf |
/etc/nginx/nginx.conf (same) |
| PHP-FPM pool | /etc/php-fpm.d/www.conf |
/etc/php/8.2/fpm/pool.d/www.conf |
| Firewall | firewall-cmd |
ufw or nftables |
| Network (static IP) | /etc/sysconfig/network-scripts/ |
/etc/netplan/*.yaml or /etc/network/interfaces |
For a quick path conversion in shell scripts, use sed or a lookup table. In Ansible, use ansible_os_family to branch logic. Test every config path on a Debian VM before deploying to production.
Warning: Apache module enabling differs between distros. On RHEL, modules are loaded via
LoadModuledirectives in config files. On Debian, usea2enmod sslanda2ensite default-sslto enable modules and sites, which symlink files frommods-availableandsites-availabletomods-enabledandsites-enabled. Scripts that echoLoadModulelines intohttpd.confwill break.
Handling Packages That Don't Translate Cleanly
Some packages have no Debian equivalent: vendor-supplied monitoring agents, proprietary database clients, or RHEL-specific utilities like subscription-manager and SELinux policy tools. You have three options: convert the RPM to a DEB with alien, build from upstream source, or containerize the RHEL-specific tool.
Using Alien to Convert RPMs (and Why It's a Last Resort)
The alien tool converts RPM packages to DEB format. Install it with apt install alien, then convert:
alien -d vendor-agent-1.2.3-1.x86_64.rpm
dpkg -i vendor-agent_1.2.3-2_amd64.deb
This works for simple, self-contained binaries with no complex dependencies. A vendor monitoring agent that drops a single binary in /usr/local/bin and a config file in /etc might install cleanly. Most packages fail because RPMs and DEBs handle library paths, init systems, and file layout differently. An RPM expecting /usr/lib64 breaks on Debian's /usr/lib/x86_64-linux-gnu. Pre- and post-install scripts that call systemctl or chkconfig with RHEL-specific assumptions will error out. After installing with dpkg -i, check for errors:
dpkg -l | grep vendor-agent
systemctl status vendor-agent
If the service fails to start or the binary segfaults, alien didn't work. Real example: a proprietary database backup tool ships only RPMs. Converting with alien produced a package that installed but crashed at runtime due to missing libc.so.6 symbol versions. The vendor didn't support Debian, so the solution was building from source.
Building from Source When No DEB Exists
If the upstream project provides source tarballs or a Git repository, build and install to /opt or /usr/local to avoid conflicts with APT-managed files:
wget https://example.com/vendor-tool-1.2.3.tar.gz
tar xzf vendor-tool-1.2.3.tar.gz
cd vendor-tool-1.2.3
./configure --prefix=/opt/vendor-tool
make
sudo make install
This keeps the tool isolated. Create a systemd unit in /etc/systemd/system/vendor-tool.service to manage it. For dependencies, install dev packages: if the build needs libssl, install libssl-dev. Debian's build-essential metapackage provides gcc, make, and libc headers. If the project uses CMake or Meson, install cmake or meson. Document the build steps in your migration notes so the next admin knows how to rebuild or upgrade the tool.
Containerizing RHEL-Specific Tools
For tools that absolutely require RHEL libraries or file layouts, run them in a Rocky Linux or Fedora container on your Debian host. Use Podman or Docker:
podman run -d --name rhel-tool \
-v /data:/data:Z \
rockylinux:9 \
/usr/local/bin/vendor-tool --config /data/config.yaml
This avoids forcing incompatible packages onto Debian. The tool runs in its native environment, accessing host data via volume mounts. For desktop applications with no server equivalent, consider Flatpak or Snap: both are distro-agnostic and bundle dependencies. Install Flatpak with apt install flatpak, then flatpak install flathub com.example.App. Snap works similarly: snap install app-name. These solve the "vendor ships only RPMs" problem for end-user software.
Testing and Rollout Strategy
Spin up a Debian or Ubuntu VM matching your target production environment. Install the translated package list and compare the result to your original DNF inventory. Run smoke tests on every critical service before touching production.
Building a Parallel Test System
Use the same VM platform or container runtime as production. If your production Rocky servers run on KVM, deploy a Debian 12 KVM guest. If you use Docker Compose, build a Debian-based image. Install your translated packages:
apt update
apt install -y apache2 mariadb-server php php-mysql cron ufw
Compare installed packages to your original DNF inventory. Export the APT package list:
dpkg -l | awk '/^ii/ {print $2, $3}' > debian-packages.txt
Cross-reference with your installed-packages.txt from the DNF system. Missing packages indicate unmapped names or packages you decided to skip. Extra packages are dependencies pulled in by APT. For a detailed comparison, use comm or diff on sorted lists of package names.
Package Verification and Smoke Tests
After installation, verify each service starts and responds. For Apache:
systemctl status apache2
apachectl -t
curl http://localhost
The apachectl -t syntax check catches config errors. For MariaDB:
systemctl status mariadb
mysql -u root -p -e "SELECT VERSION();"
Check that your application connects to the database with the same credentials. For cron jobs, verify the crontab migrated:
crontab -l
systemctl status cron
Run a representative workload: deploy your app, hit the API endpoints, check logs in /var/log/apache2/ and /var/log/syslog. If your app uses environment-specific paths or commands, update them now. A script that calls /usr/sbin/apachectl works on both distros, but one that hardcodes /etc/httpd/conf.d/ needs fixing.
Documenting the Final Mapping
Maintain a migration log as a CSV or Markdown table. Columns: DNF package, APT package(s), notes on differences, test status. Example:
| DNF Package | APT Package | Notes | Tested |
|---|---|---|---|
| httpd | apache2 | Config in /etc/apache2/, use a2enmod for modules | โ |
| mod_ssl | apache2 (built-in) | Enable with a2enmod ssl |
โ |
| cronie | cron | Same functionality | โ |
| firewalld | ufw | Syntax differs; rewrote rules | โ |
This log serves as a reference for future migrations or rollback planning. For production rollout, deploy to dev first, then staging, then prod. If downtime is unacceptable, use a blue-green deployment: build the Debian system in parallel, migrate data, switch traffic with a load balancer cutover. One warning: database data directory migrations (PostgreSQL's /var/lib/pgsql to /var/lib/postgresql, MariaDB version upgrades) are orthogonal to package management. Handle them separately with pg_upgrade or mysql_upgrade.
Post-Migration Package Maintenance
APT's update and upgrade workflow differs from DNF in command syntax and behavior. Show upgradable packages:
apt update
apt list --upgradable
The apt update step refreshes repository metadata (like dnf makecache). Apply updates with:
apt upgrade
This installs new versions of installed packages but does not remove or install new packages to satisfy changed dependencies. For a full upgrade that handles dependency changes (like dnf upgrade or dnf distro-sync), use:
apt full-upgrade
The older apt-get dist-upgrade is equivalent to apt full-upgrade. Remove unneeded dependencies with:
apt autoremove
This is the same concept as dnf autoremove. For an inventory of installed packages, use apt list --installed or dpkg -l. The apt(8) man page documents all commands and options. Set up unattended-upgrades for automatic security patching, equivalent to dnf-automatic:
apt install unattended-upgrades
dpkg-reconfigure -plow unattended-upgrades
This enables daily checks and automatic installation of security updates. Monitor security advisories: Debian's security mailing list (debian-security-announce) and Ubuntu's USN feed publish CVE fixes. Check the APT package health auditing guide for tools to track outdated and vulnerable packages. Use apt list --upgradable in monitoring scripts to alert when patches are available. For APT history, read /var/log/apt/history.log and /var/log/dpkg.log, which record every install, upgrade, and removal with timestamps.
Migration Checklist and Next Steps
Before you declare the migration complete, verify these steps:
- Export DNF package inventory with
dnf repoquery --installedanddnf history userinstalled. - Map every package name to its APT equivalent using search, apt-file, and upstream documentation.
- Translate custom repositories: download GPG keys, convert .repo files to sources.list entries.
- Update automation scripts: replace
dnfwithapt-getorapt, change package names, fix config file paths. - Convert Ansible playbooks: swap
dnfmodule forapt, update package lists, test on a staging host. - Handle packages with no DEB equivalent: try alien, build from source, or containerize.
- Deploy to a test VM, run smoke tests, document differences in a migration log.
- Roll out to dev, staging, then production with a phased approach or blue-green cutover.
- Configure
unattended-upgradesand monitor security advisories.
For deeper coverage of APT internals, dependency resolution, and troubleshooting, see the Debian System Administration eBook. It covers advanced topics like pinning, backports, and building custom DEBs. After migration, audit your package inventory regularly with the Linux package auditing tool to catch outdated or orphaned packages. If you're consolidating mixed infrastructure, compare Ubuntu Server vs Debian to decide on a standard distro going forward.
Frequently Asked Questions
Can I directly convert an RPM package to DEB and install it?
Yes, with the alien tool: alien -d package.rpm then dpkg -i package.deb. This works only for simple, self-contained binaries with no complex dependencies. Most packages fail because RPMs and DEBs handle library paths, init scripts, and file layouts differently. Pre/post-install scripts often assume RHEL-specific paths or commands. Use alien only as a last resort; build from source or find a Debian equivalent instead.
What happens to packages installed from EPEL when I migrate to Ubuntu?
EPEL has no direct Ubuntu equivalent. Many EPEL packages already exist in Ubuntu's universe or multiverse repositories, or in Debian's main/contrib repos. Search with apt search package-name. For packages not in default repos, check if the upstream project publishes a DEB repo (PostgreSQL, Docker, Nginx all do). If no DEB exists, build from source or find a functional replacement in Ubuntu's archives.
Do DNF package groups have an equivalent in APT?
Partially. DNF groups like "Development Tools" map loosely to Debian metapackages like build-essential or tasksel selections. List DNF group contents with dnf group info "Group Name", then install the individual packages on Debian. Ubuntu's tasksel offers task-based installations (LAMP server, Mail server), but the groupings differ. You'll need to manually map group members to APT packages.
How do I find the Debian package name for an RHEL package?
Use apt search keyword or apt-file search /path/to/binary on a Debian system. For offline lookup, search packages.debian.org by package name or file path. Cross-distro search at pkgs.org shows package names across RHEL, Debian, and Ubuntu. Common translations: httpd โ apache2, cronie โ cron, bind โ bind9. Maintain a mapping file for your inventory.
Will my systemd service files work after migrating from Fedora to Debian?
Mostly yes. Systemd unit syntax is identical across distros, but service names differ: httpd.service becomes apache2.service. If your unit files reference absolute paths to binaries or config files, verify those paths exist on Debian. RHEL uses /etc/httpd/, Debian uses /etc/apache2/. Custom units in /etc/systemd/system/ migrate fine if they don't hardcode distro-specific paths. Test with systemctl status service-name after migration.