Fix broken packages in apt: unmet dependencies
Broken packages in apt can mean two faults. Learn to tell an unconfigured .deb from a resolver refusal, then find the real blocker before you blame a hold.
Two faults share the name "broken packages"
To fix broken packages in apt, first find out which of two faults you have. The first is a dpkg state. A package is on disk and unpacked, but it is not configured, because something it depends on is missing. The second is apt's resolver refusing a request and reporting unmet dependencies. Each fault needs a different fix. The second one rarely means that you broke anything.
This guide stages both faults on purpose with two tiny home-made packages. You see each fault, and then you repair it. The commands are written for Ubuntu 26.04 LTS, which ships apt 3.2.0. Debian uses the same apt project, so the steps carry over, but the exact messages can differ between apt versions. Run the staging steps on a test server, not on a machine you care about.
dpkg and apt do different jobs
The dpkg man page calls dpkg "a medium-level tool to install, build, remove and manage Debian packages". It also names the front-end: "The primary and more user-friendly front-end for dpkg as a CLI (command-line interface) is apt(8)". dpkg installs the package file you give it. It does not download anything. The man page puts it this way: "You will need some other application to actually download and install the requested packages."
This split explains both faults. dpkg records a state for every package. apt plans a set of changes that satisfies every dependency, and dpkg carries out the installs. Fault one lives in dpkg's states. Fault two lives in apt's plan.
Which kind of broken package do you have?
Two read-only commands tell the faults apart.
sudo dpkg --audit
dpkg -l | grep -v '^ii'dpkg --audit "searches for packages that have been installed only partially on your system". dpkg -l lists every package dpkg knows about. The grep hides the lines that start with ii. What is left is the column header plus every package that is not fully installed.
Read the first letters of each remaining line. The dpkg-query man page says "The first three columns of the output show the desired action, the package status, and errors, in that order." These are the letters that matter here:
- Second letter
U: Unpacked. The package is unpacked but not configured. - Second letter
F: Half-configured. Configuration started but did not finish. - Second letter
H: Half-installed. Installation started but did not finish. - Third letter
R: Reinst-required. dpkg wants the package reinstalled. rcat the start: desired action Remove, status Config-files. The package was removed and only its config files remain. This one is harmless.
If dpkg --audit lists a package, or a line shows U, F, H or R, you have fault one. If both checks are clean and apt still refuses to install something, you have fault two.
Stage fault one: a package unpacked without its dependency
Build a package that depends on jq, a small JSON tool from the Ubuntu archive. First confirm that jq is not installed:
dpkg -l jqIf that prints a line starting with ii, jq is already installed. Pick a different small package that you do not have, and put its name in the Depends: line below.
mkdir -p ~/debs/demo-needs-jq/DEBIAN
cat > ~/debs/demo-needs-jq/DEBIAN/control <<'EOF'
Package: demo-needs-jq
Version: 1.0
Architecture: all
Maintainer: Demo <demo@example.com>
Depends: jq
Description: Demo package that depends on jq
EOF
cd ~/debs
dpkg-deb --build demo-needs-jq demo-needs-jq_1.0_all.debThe deb-control man page lists Package, Version and Architecture as required fields. It marks Maintainer and Description as "recommended". They cost two lines, so keep them. dpkg-deb --build needs a directory that contains a DEBIAN subdirectory with the control file in it. It checks the control file for syntax errors and prints the name of the package it builds. A typo in a field name shows up at this step, before anything touches the system.
Now install the package with dpkg alone, and record the exit status:
sudo dpkg -i ~/debs/demo-needs-jq_1.0_all.deb; echo "exit status: $?"The dpkg man page lists the steps of --install. dpkg extracts the control files, unpacks the new files, and configures the package as the last step. jq is not on the system, and dpkg does not fetch it. Read what dpkg printed. Then check which state the package reached:
dpkg -l demo-needs-jq
sudo dpkg --auditA line starting with iU means the desired action is Install and the status is Unpacked. The files are in place, but the configure step did not complete. The dpkg man page defines this state plainly: "The package is unpacked, but not configured." A non-zero exit status from dpkg -i plus a package that is not ii is fault one.
What does apt do while a package is half-installed?
Try to install any other package now:
sudo apt install treeThe apt-get man page states the rule: "APT itself does not allow broken package dependencies to exist on a system." apt's source code has this error for the case:
Unmet dependencies. Try 'apt --fix-broken install' with no packages (or specify a solution).The source also has the hint "You might want to run 'apt --fix-broken install' to correct these." Your apt version decides the exact wording, so read your own output and do not expect a perfect match with these strings. The meaning stays the same. An existing breakage must be fixed before apt accepts new work. This is also why fault one can look like fault two. A request for an unrelated package fails, and the real cause is a package that was left unpacked earlier.
Repair it with apt --fix-broken install
sudo apt update
sudo apt --fix-broken installThe apt-get man page documents this option as -f, so sudo apt-get -f install is the same request in apt-get's spelling. The man page describes it as "Fix; attempt to correct a system with broken dependencies in place." With no package names, apt is free "to deduce a likely solution" by itself. If you do name packages, "these have to completely correct the problem."
Read the list of changes that apt prints. Here the right plan installs jq, and then dpkg can finish configuring demo-needs-jq. If the plan removes a package you rely on, answer no. Then work out why the dependency cannot be installed, with the blocker checks later in this guide.
Check the result:
dpkg -l demo-needs-jq jq
sudo dpkg --auditBoth lines should start with ii, and dpkg --audit should list no packages.
Sometimes --fix-broken finds no plan at all. The apt-get man page says a badly broken dependency structure can need manual work, "which usually means using dpkg --remove to eliminate some of the offending packages". For the demo package, that command is:
sudo dpkg --remove demo-needs-jqRemoving the package with the missing dependency clears the fault, because no installed package asks for jq any more. Run sudo apt --fix-broken install again afterwards to confirm that apt has nothing left to fix.
Stage fault two: unmet dependencies apt cannot meet
The second fault never reaches dpkg. Build a package whose dependency no archive can meet, because it needs bash version 99 or later:
mkdir -p ~/debs/demo-impossible/DEBIAN
cat > ~/debs/demo-impossible/DEBIAN/control <<'EOF'
Package: demo-impossible
Version: 1.0
Architecture: all
Maintainer: Demo <demo@example.com>
Depends: bash (>= 99)
Description: Demo package that needs a bash version that does not exist
EOF
cd ~/debs
dpkg-deb --build demo-impossible demo-impossible_1.0_all.deb
sudo apt install ./demo-impossible_1.0_all.deb; echo "exit status: $?"bash (>= 99) is valid syntax. The deb-control man page lists the accepted version relations, including >> for greater than, << for less than and >= for greater than or equal to. Keep the leading ./ on the file name. apt's source treats an argument as a local file only when it starts with / or .. Without it, apt searches the archive for a package with that name.
This time apt plans the dependencies before anything is installed. No bash version in the archive satisfies >= 99, so apt has nothing it can install and refuses the request. Read the output and the exit status line.
Why does apt say "you have held broken packages"?
This is the message that most forum answers quote:
Unable to correct problems, you have held broken packages.It comes from the classic resolver in apt's source, pkgProblemResolver in apt-pkg/algorithms.cc. The resolver returns this text whenever packages are still broken after it has finished trying. The source comment just above it reads // See if this is the result of a hold. That is a guess written into the code. It is not a check of your system. An impossible version or a conflict can end on this line just as well, with no hold anywhere.
Your server may not print this line at all. The changelog entry for apt 3.1.0 reads "Ubuntu: Enable 3.0 solver by default in apt CLI tools", and Ubuntu 26.04 ships apt 3.2.0. The 3.0 solver reports failures in its own format. The apt 2.9.32 changelog says it was changed to "Produce more human-readable dependency errors in 3.0 solver." So the text on 26.04 can differ from the classic message. If you moved a server through the upgrade from Ubuntu 24.04 to 26.04, run apt --version before you search for an error string you remember.
Whatever the wording, check for holds:
apt-mark showholdThe apt-mark man page says showhold prints "a list of packages on hold". An empty result means that nothing on the server is held. In that case the word "held" in the message pointed you the wrong way.
How do you find the real blocker?
Work through four checks, in this order:
- Rule out fault one with
sudo dpkg --audit. A package left unpacked earlier makes apt refuse unrelated requests. Fix that first withsudo apt --fix-broken install. - Look at what the archive offers for the dependency that apt named, with
apt-cache policy bash. - Ask for the named dependency on its own, with
sudo apt install bash. - Check for real holds with
apt-mark showhold.
Given a package name, apt-cache policy prints detailed information about how apt selects a version of that package. Its output has an Installed: line, a Candidate: line and a Version table:. The candidate is the version apt would install. Compare it with the version that the Depends: line demands. For the demo package, no version in the table reaches 99, so apt has nothing it could install. That gap is the real blocker. Know the limit of this command: it shows what the archive offers for one package, and it does not show which other package demands more.
Installing the dependency on its own narrows the search. Here sudo apt install bash either leaves bash alone or upgrades it to the candidate. In both cases bash itself is fine, and the demo package still cannot install. That puts the fault on the version requirement. On a real server, the named dependency can itself be blocked. When you ask for it directly, apt explains that package's own problem, one level down. Repeat this until apt names something concrete, such as a version your archive does not carry, or a held package.
The demo has the same shape as a real case you may meet: a .deb built for a different release that names library versions your release does not carry. apt-cache policy on that library shows the gap in the same way.
Nothing needs cleaning up after this fault. apt refused before dpkg ran, so demo-impossible was never unpacked. Run sudo dpkg --audit to confirm. It should list no packages.
How to hold a package with apt-mark, and release it
A real hold is a choice someone made on purpose. The apt-mark man page says hold marks a package "as held back, which will prevent the package from being automatically installed", upgraded or removed. Hold jq, which fault one installed:
sudo apt-mark hold jq
apt-mark showhold
dpkg -l jqshowhold should now print jq. The man page says apt-mark changes dpkg selections, and hold is one of them. So dpkg -l jq shows h as the first letter, which the dpkg-query man page defines as the Hold desired action.
Release the hold:
sudo apt-mark unhold jq
apt-mark showholdThe man page says "unhold is used to cancel a previously set hold on a package to allow all actions again." showhold should now print nothing.
When a hold is the real blocker, the classic message guessed right. A held library cannot be upgraded, so a new package that needs a newer version of that library cannot install. Decide whether the reason for the hold still stands. If it does not, unhold the package and install. If it does, leave the new package out. Some people hold kernel packages. If you are one of them, choosing which kernel boots on a VPS covers the boot side of that choice. On Rocky Linux and AlmaLinux, the same job is done by pinning packages with dnf versionlock. If a server runs automatic updates with unattended-upgrades, run apt-mark showhold before you decide that the updater skipped a package for no reason.
What this guide does not do
dpkg has --force-* options. The dpkg man page says "These options are mostly intended to be used by experts only." This guide does not teach them. It also never deletes lock files. If apt reports a lock, find out which process holds it, and let that process finish.
If the breakage appeared in the middle of a release upgrade, start with recovering a failed Ubuntu release upgrade instead of this guide.
Clean up the demo packages
sudo apt remove demo-needs-jq jq
rm -rf ~/debs
dpkg -l demo-needs-jq jqAfter the removal, neither line should start with ii.
FAQ
What is the difference between a broken package and unmet dependencies?
In the dpkg sense, a broken package is one that dpkg started but did not finish, such as a package that is unpacked but not configured. sudo dpkg --audit lists those packages, and sudo apt --fix-broken install repairs them. Unmet dependencies means that apt's resolver refused a request, because no set of available versions satisfies it. To repair that, find the dependency that cannot be met. Use apt-cache policy on it, and try to install it on its own.
Does "you have held broken packages" mean I put a package on hold?
Usually not. In apt's source, the classic resolver returns that text whenever packages are still broken after resolution, and the code comment above it only guesses that a hold is the cause. Run apt-mark showhold. If it prints nothing, no package is held. The real blocker is then a dependency version or a conflict that apt cannot satisfy.
How do I hold a package in apt?
Run sudo apt-mark hold <package>. A held package is not automatically installed, upgraded or removed. apt-mark showhold lists every held package, and sudo apt-mark unhold <package> cancels the hold. While the hold is in place, dpkg -l <package> shows h as the first letter.
Is apt --fix-broken install safe to run?
With no package names, apt works out a likely solution and prints the changes it plans. Read that list before you agree to it. The plan may install the missing dependency, or it may remove the package that needs it. If it removes something you depend on, answer no, and find the blocker with apt-cache policy first.
Do these steps work on Debian?
Debian and Ubuntu use the same apt project, so the two faults and the repair steps carry over. The exact messages depend on the apt version and on which solver that version uses by default. Compare against your own output, not against strings copied from a forum.