You upgraded on Tuesday. By Thursday, something broke.
You run a routine package update to keep your system secure. The terminal scrolls, the progress bar finishes, and you move on. Two days later, a critical service fails to start, a desktop extension crashes, or a development tool refuses to load. The update worked fine on your test machine, but your daily driver has different opinions. You need to step back. Fast.
What is actually happening
DNF does not just download and extract RPM archives. It maintains a local SQLite database that logs every package operation as a discrete transaction. Each transaction receives a sequential ID, a timestamp, the exact command that triggered it, and a manifest of what changed. Think of it as a ledger for your software stack. When you install, update, or remove a package, DNF writes a forward entry. When you undo, DNF reads that entry and calculates the inverse operations. It does not take filesystem snapshots. It reverses package states by reinstalling older versions or removing newer ones.
This design keeps the history database lightweight. It also means DNF tracks package files, not configuration files. If a post-install script rewrites /etc/nginx/nginx.conf, undoing the transaction will downgrade the package but leave your edited config file intact. The package manager trusts you to manage /etc. Files in /usr/lib ship with the package and get overwritten automatically. Files in /etc are yours to keep. Run journalctl -xe first. Read the actual error before guessing which package caused the regression.
The fix
Start by checking what DNF actually recorded. The history database lives in /var/lib/dnf/history/. You interact with it through the history subcommand. You do not need root privileges to read the log. You only need sudo when you modify the system.
Here is how to pull the recent transaction log and identify the problematic entry.
dnf history list
# Displays the last 20 transactions by default
# Columns show ID, Date, Action, and package count
# Look for the ID that matches the date of your upgrade
# Press q to exit if you pipe to less
The output gives you a quick overview. You will see IDs like 42, 43, 44. The action column shows Install, Upgrade, or Erase. If you need to know exactly which packages changed, drill into the specific ID.
Here is how to inspect the exact package versions involved in a single transaction.
dnf history info 42
# Replaces 42 with your actual transaction ID
# Shows the full list of installed, updated, or removed packages
# Includes version numbers and repository sources
# Use --show-transactions to see the exact dnf command that ran
The info command breaks down the transaction into three lists: installed, updated, and removed. You will see the old version and the new version side by side for upgrades. If the output is too long, pipe it to less. Do not guess the ID. Pick the exact transaction that introduced the regression.
Once you have the ID, you can reverse it. DNF calculates the inverse operations and presents a dry run before touching anything. The preview shows exactly which RPMs will be downloaded and which will be removed.
Here is how to safely reverse a specific transaction.
sudo dnf history undo 42
# Replaces 42 with your target transaction ID
# DNF calculates reverse operations and shows a preview
# Press y to proceed after verifying the package list
# The undo operation itself becomes a new forward transaction
If you only want to step back one transaction and do not care about the ID, use the last keyword. DNF resolves it to the highest ID in the database.
Here is how to undo the most recent transaction without looking up the ID.
sudo dnf history undo last
# Targets the highest transaction ID automatically
# Useful when you just ran a single command and want to revert it
# Still shows the full preview before applying changes
# Fails gracefully if the database is empty
DNF will download the older RPMs, verify dependencies, and apply the rollback. It handles the RPM database updates automatically. You do not need to run rpm --rebuilddb afterward. The transaction log updates itself to record the undo operation as a new forward entry. Snapshot the system before the upgrade. Future-you will thank you when the undo command needs a clean baseline.
Verify it worked
Rollbacks are not magic. You need to confirm the system actually returned to the expected state. Check the package version directly. DNF reports success when the RPM database is consistent, but the running processes might still hold the old binaries in memory.
Here is how to verify the package version after the undo operation.
dnf list installed <package-name>
# Replaces <package-name> with the affected software
# Confirms the version number matches your pre-upgrade state
# Run this before restarting services or rebooting
# Use dnf repoquery --installed to check multiple packages
If the package was part of a larger group or triggered a service change, check the service status. DNF does not restart units automatically after an undo. You will need to reload systemd or restart the affected daemon manually. Run systemctl status <unit> to confirm it is running with the downgraded binaries. Reboot before you debug. Half the time the symptom is gone once the libraries match the binaries again.
Common pitfalls and what the error looks like
Undo operations fail when the original repository is gone or when dependencies have shifted. If you undo a transaction from three months ago, the older RPMs might no longer exist in the enabled repositories. DNF will abort with a dependency resolution error.
The dnf history undo command will refuse to proceed and print Error: Package <name> is not available or Error: Nothing to do. The missing repository is the usual culprit. Enable the Fedora archives or point DNF to a local mirror that still hosts the older release. You can also try dnf downgrade <package> if the history database is out of sync with the available metadata.
Configuration drift is the second most common trap. DNF tracks package files, not user edits. If you modified /etc/ssh/sshd_config after the package installed, undoing the transaction will downgrade the RPM but leave your custom config untouched. The service might fail to start because the old binary does not understand a new directive, or the new binary complains about a missing feature. Always check /etc files after a rollback. Run diff -u /etc/<file>.rpmsave /etc/<file> if DNF preserved your changes during a previous upgrade.
SELinux contexts can also break after a rollback. If a package upgrade installed new binaries in a non-standard path, the undo operation removes them but does not automatically relabel the directory. You will see Permission denied errors in the journal. Run restorecon -Rv /path/to/directory to reset the contexts. Trust the package manager for binaries. Handle /etc and SELinux manually.
Dependency cycles are the third failure mode. DNF calculates the inverse transaction by reversing the package states. If package A depends on package B, and you undo the installation of A, DNF will also remove B. If B is required by another package you did not touch, the undo will abort.
The terminal will print Error: Transaction test error: package <name> requires <dependency>. The conflict is intentional. DNF protects you from breaking the system. Read the dependency tree with dnf repoquery --requires <package> before forcing. Never use --skip-broken on an undo operation. It leaves the RPM database in an inconsistent state.
When to use this vs alternatives
Use dnf history undo when you need to reverse a specific batch of package changes and the original RPMs are still available in your repositories. Use dnf downgrade <package> when you only want to step back one or two packages without touching the rest of the transaction. Use rpm-ostree rollback when you are running Fedora Silverblue and want to revert the entire immutable boot image to the previous deployment. Stay on manual package removal and reinstall when the transaction database is corrupted or when you are troubleshooting a broken dependency chain that DNF cannot resolve automatically.