
Linux command: apt
Every Debian or Ubuntu server you have ever administered was built, updated and repaired with one command. apt installs your web server, patches the security hole you read about this morning, and quietly removes the library nothing needs any more. Most people learn three of its subcommands and stop, which is a shame, because the parts they skip are exactly the parts that explain why a server sometimes refuses to upgrade a package, why the disk filled up, and why the same command produced different results on two machines that should be identical.
1. The Basics
The job of apt is to get software onto your system and keep it current, without you having to work out what else that software needs. You ask for one package; apt works out the twenty others it depends on, downloads them all, and installs them in the right order.
$ sudo apt update
$ sudo apt install nginx
Those two lines are the whole daily workflow, and the order matters. They do completely different things, and mixing them up is the most common beginner mistake with apt:
apt updaterefreshes the catalogue. It downloads the current list of available packages and versions from your configured repositories. It installs nothing.apt installandapt upgradeact on that catalogue. They download and install actual software.
Think of update as reading the latest price list and install as placing the order. Order from last year's list and you will be told the item does not exist.
Underneath, apt is not the program that unpacks files onto your disk. That is dpkg, the Debian package tool, and it has been there since long before apt. dpkg installs one .deb file and complains if something is missing. apt sits on top, works out which files are needed, fetches them over the network, and hands them to dpkg in a workable order.
you -> apt decides what is needed, downloads it
|
v
dpkg unpacks and configures individual .deb files
|
v
your filesystem
The command that turns "install a web server" into a solved dependency graph. Behind two subcommands sit repositories, cryptographic signatures, pinning, phased rollouts, and a package database with 3,000 entries on an ordinary desktop.
This article starts with installing and removing software and builds up to repository configuration, signing keys, held packages, and the reasons apt sometimes declines to do what you asked.
Back to topThe right mental model: apt is a dependency solver with a downloader attached. dpkg does the installing. Almost every confusing apt message is apt telling you that the graph it was asked to solve has no acceptable solution.
2. Where the Name Comes From
APT stands for Advanced Package Tool.
apt = Advanced Package Tool
"Advanced" was a claim about its predecessor rather than marketing. Before APT, installing software on Debian meant dpkg (Debian PacKaGe) for single files and a menu-driven front end called dselect that people remember without affection. The advance was automatic dependency resolution over a network.
The confusing part is not the name but the family. Several commands share it, and they are genuinely different programs:
| Command | What it is | When to use it |
|---|---|---|
dpkg |
The low-level tool that installs one .deb and tracks what is installed |
Installing a downloaded .deb, or querying which package owns a file |
apt-get |
The original APT command line, with a deliberately stable interface | Scripts, Dockerfiles, automation |
apt-cache |
The query half of the original interface | Searching and inspecting package metadata in scripts |
apt |
A friendlier front end combining the most-used parts of both, added in 2014 | Typing at a terminal, interactively |
aptitude |
A separate, more powerful front end with a full-screen interface | Untangling hard dependency conflicts; not installed by default |
They are not aliases of each other. apt and apt-get are separate binaries with different sizes and different behaviour, and section 7.1 shows a difference you can see for yourself.
The short version of when to use which: type apt, script apt-get. The reason for that rule is one of the more interesting things about the tool, and section 7.1 explains it.
3. A Short History
APT is younger than Debian and younger than dpkg. Debian's release manager, Brian White, commissioned the work in 1997 because installing software was the distribution's weakest point. The first working code was a test program called dpkg-get, and much of the early development happened on IRC, which is why the record is patchy.
The Debian changelog still carries the very first entry, and it is charmingly small:
apt (0.0.1) unstable; urgency=low
* Initial Release.
-- Scott K. Ellis <This email address is being protected from spambots. You need JavaScript enabled to view it. > Tue, 31 Mar 1998 12:49:28 -0500
The next entry, three days later, notes that 0.0.1 "was released only on IRC". APT first shipped to users in Debian 2.1, released on 9 March 1999, and it changed how the distribution felt to use.
For the next sixteen years the command you typed was apt-get. The plain apt command arrived only in April 2014, and the release that introduced it is named after the anniversary:
apt (1.0) unstable; urgency=low
The "Happy birthday and 10000b years in the making" release
...
[ Michael Vogt ]
* install apt binary
* add apt.8.xml manpage
-- Michael Vogt <This email address is being protected from spambots. You need JavaScript enabled to view it. > Tue, 01 Apr 2014 15:48:46 +0200
10000b is binary for 16. The first release was 31 March 1998; version 1.0 landed on 1 April 2014, sixteen years and one day later. That is the entire reason a second command exists: after sixteen years of apt-get and apt-cache, the developers added one command that combined the parts people actually used, with progress bars and colour.
| Era | Milestone |
|---|---|
| 1993 onward | dpkg handles individual packages on Debian; dependencies are the user's problem |
| 1997 | Brian White commissions a better front end; the prototype is called dpkg-get |
| 31 March 1998 | apt 0.0.1, released "only on IRC" by Scott K. Ellis |
| 9 March 1999 | APT ships to users in Debian 2.1 |
| 1 April 2014 | apt 1.0 installs the apt binary, "10000b years in the making" |
| 2020 | apt 2.0 adds search patterns (apt-patterns), replacing loose regex matching |
| 2021 | apt 2.1.16 adds phased updates and automatic removal of unused kernels on full-upgrade |
| 2021 | apt 2.3.12 refuses to remove essential packages at all, after a famous accident |
| 2024 | Ubuntu 24.04 moves its repository configuration to the deb822 .sources format |
That 2021 entry has a story behind it, and section 7.3 tells it.
Back to top4. Simple Use Cases
4.1 Refreshing the Catalogue
Almost every apt session starts here. apt update contacts each configured repository and downloads the current package lists.
$ sudo apt update
Get:1 http://security.ubuntu.com/ubuntu noble-security InRelease [126 kB]
Get:2 http://archive.ubuntu.com/ubuntu noble InRelease [256 kB]
Get:3 http://security.ubuntu.com/ubuntu noble-security/universe amd64 Packages [1522 kB]
...
Reading package lists...
Building dependency tree...
Reading state information...
3 packages can be upgraded. Run 'apt list --upgradable' to see them.
Two words in that output are worth knowing. Get: means apt downloaded a list because it changed. Hit:, which you will see on a second run, means the list was already current and nothing was transferred.
The last line is a prompt to look closer, and the command it suggests is the useful one:
$ apt list --upgradable
Listing...
libssl3t64/noble-updates,noble-security 3.0.13-0ubuntu3.12 amd64 [upgradable from: 3.0.13-0ubuntu3.11]
libsystemd0/noble-updates,noble-security 255.4-1ubuntu8.17 amd64 [upgradable from: 255.4-1ubuntu8.16]
libudev1/noble-updates,noble-security 255.4-1ubuntu8.17 amd64 [upgradable from: 255.4-1ubuntu8.16]
Each line gives the package, which repository the new version comes from, the new version, and the version you have. Seeing noble-security in that list tells you the upgrade is a security fix, which is usually enough to decide it should not wait.
4.2 Why "Unable to Locate Package" Almost Never Means What It Says
This is the error that sends new users looking for a different package name, and it is nearly always the wrong conclusion. Here is a completely fresh Ubuntu system with no package lists yet:
$ ls /var/lib/apt/lists/ | wc -l
0
$ sudo apt-get install -y tree
Reading package lists...
Building dependency tree...
Reading state information...
E: Unable to locate package tree
The tree package exists and always has. apt simply has no catalogue to look in, so as far as it knows, no packages exist at all. One apt update fixes it.
This is why every Dockerfile you have ever read joins the two commands together. In a container the package lists are deliberately empty to keep the image small, so the update is not optional:
RUN apt-get update && apt-get install -y --no-install-recommends nginx \
&& rm -rf /var/lib/apt/lists/*
There is a second reason for the && that catches people out later. Docker caches each RUN as a layer. Split across two instructions, the update layer can be reused from cache weeks later while the install layer runs fresh, so you install today's packages against a stale catalogue and get errors about versions that no longer exist. Joined into one instruction, they are cached and invalidated together. The final rm -rf deletes the catalogue again, because keeping several hundred megabytes of package lists in a shipped image serves nobody.
4.3 Installing Software
Give apt a package name. Before it does anything it prints a summary and waits, and that summary is worth reading rather than reflexively pressing Enter:
$ sudo apt install tree
Reading package lists...
Building dependency tree...
Reading state information...
The following NEW packages will be installed:
tree
0 upgraded, 1 newly installed, 0 to remove and 3 not upgraded.
Need to get 47.4 kB of archives.
After this operation, 111 kB of additional disk space will be used.
Read that summary line as four separate counts. Upgraded and newly installed are what you asked for. To remove is the one to check, because a package you did not mention appearing there means apt has decided something must go to satisfy your request. Not upgraded counts packages with newer versions available that this command is leaving alone.
Add -y to answer the confirmation automatically, which is what scripts do. Do not put it in your fingers for interactive use, because the confirmation prompt is the last chance to notice an unexpected removal.
4.4 Finding Software
apt search looks through package names and descriptions, and apt show prints the detail:
$ apt search '^nginx' # anchored, or you get hundreds of matches
$ apt show tree
Package: tree
Version: 2.1.1-2ubuntu3.24.04.2
Priority: optional
Section: universe/utils
Origin: Ubuntu
Installed-Size: 111 kB
Depends: libc6 (>= 2.38)
Homepage: http://oldmanprogrammer.net/source.php?dir=projects/tree
Download-Size: 47.4 kB
Two fields there answer real questions. Section: universe/utils tells you the package comes from Ubuntu's community-maintained universe repository rather than the officially supported main, which matters for security-update guarantees on a server. Installed-Size is the disk cost after installation, as opposed to Download-Size.
4.5 Removing Software
There are two removal commands and the difference catches almost everybody. Watch the package state before and after, using dpkg's own listing:
$ dpkg -l wget
ii wget 1.21.4-1ubuntu4.4 amd64 retrieves files from the web
$ sudo apt remove wget
$ dpkg -l wget
rc wget 1.21.4-1ubuntu4.4 amd64 retrieves files from the web
The state changed from ii to rc. Those two letters are not arbitrary, and dpkg prints its own legend if you let it:
$ dpkg -l bash
Desired=Unknown/Install/Remove/Purge/Hold
| Status=Not/Inst/Conf-files/Unpacked/halF-conf/Half-inst/trig-aWait/Trig-pend
|/ Err?=(none)/Reinst-required (Status,Err: uppercase=bad)
||/ Name Version Architecture Description
+++-=====-================-============-===========================
ii bash 5.2.21-2ubuntu4 amd64 GNU Bourne Again SHell
The first letter is what you want and the second is what is. So ii means "install, installed", and rc means "remove, config-files": you asked for removal, and what remains is configuration. The same legend explains iU (unpacked but not configured) and iF (half-configured), which are the states an interrupted install leaves behind. An uppercase letter always means something needs attention.
In the rc case, the leftover really is on disk:
$ ls /etc/wgetrc
/etc/wgetrc
That is deliberate and usually helpful, because reinstalling later restores your settings. It is unhelpful when you removed the package precisely to start again from a clean configuration. purge takes the configuration too:
$ sudo apt purge wget
Purging configuration files for wget (1.21.4-1ubuntu4.4) ...
$ dpkg -l wget
dpkg-query: no packages found matching wget
A useful habit on a server: use remove when you might want the software back, and purge when you are removing something you misconfigured.
5. Moderate Use Cases
5.1 upgrade Versus full-upgrade
Both install newer versions of what you already have. The difference is what they are willing to do to achieve it.
| Command | Will install new packages? | Will remove packages? |
|---|---|---|
apt upgrade |
Yes, if needed by an upgrade | No, never |
apt full-upgrade |
Yes | Yes, if that is the only way |
apt-get upgrade |
No | No |
apt-get dist-upgrade |
Yes | Yes |
full-upgrade and dist-upgrade are the same operation under two names. The safety property of plain upgrade is that it never removes anything, so if the only route to a newer package is deleting another one, it declines and leaves that package alone. When it does that, it tells you clearly.
5.2 "The Following Packages Have Been Kept Back"
This message worries people, and it is usually apt behaving correctly. You can produce it deliberately by putting a package on hold, which tells apt never to change it:
$ sudo apt-mark hold libssl3t64
libssl3t64 set on hold.
$ sudo apt upgrade
The following packages have been kept back:
libssl3t64
The following packages will be upgraded:
libsystemd0 libudev1
2 upgraded, 0 newly installed, 0 to remove and 1 not upgraded.
The hold survives reboots and every future upgrade until you release it:
$ apt-mark showhold # what is currently held
libssl3t64
$ sudo apt-mark unhold libssl3t64
Canceled hold on libssl3t64.
Holds are genuinely useful on a server where one component must stay on a known version, such as a database whose upgrade needs a maintenance window. They are also easy to forget, so apt-mark showhold belongs in your checklist when a machine mysteriously stops receiving updates.
A hold is not the only cause of a kept-back package, though. On Ubuntu the most common cause is something else entirely, and section 7.2 covers it.
5.3 Cleaning Up What Nothing Needs
When apt installs a package it records whether you asked for it or whether it came along as a dependency. Remove the thing you asked for, and the dependencies stay behind, marked as automatically installed and no longer required.
The effect is larger than people expect. Installing git on a minimal system pulls in 46 packages; removing git afterwards leaves 42 of them orphaned:
$ sudo apt autoremove
0 upgraded, 0 newly installed, 42 to remove and 2 not upgraded.
Remv git-man
Remv krb5-locales
Remv less
Remv libcurl3t64-gnutls
...
Read that list before agreeing. autoremove is the apt command most likely to surprise you, because "nothing depends on this" is not the same as "you do not want this". If you installed something manually and apt thinks it arrived as a dependency, autoremove will offer to delete it. You can correct apt's opinion:
$ sudo apt-mark manual less # I want this even if nothing depends on it
$ apt-mark showauto | wc -l # how many packages are marked automatic
Two related commands free disk space without removing software. apt clean empties the cache of downloaded .deb files, and apt autoclean removes only the ones that can no longer be downloaded:
$ du -sh /var/cache/apt/archives/ # downloaded .deb files
3.5M /var/cache/apt/archives/
$ du -sh /var/lib/apt/lists/ # the package catalogue itself
345M /var/lib/apt/lists/
That second number surprises people on small servers. The catalogue is large because it describes every package in every configured repository, and it grows with each extra repository you add.
5.4 What Did I Change Last Week?
apt keeps a log of every transaction, which is the first place to look when a server starts behaving differently and nobody admits to touching it:
$ less /var/log/apt/history.log
Start-Date: 2026-08-11 09:48:42
Commandline: /usr/bin/unattended-upgrade
Upgrade: libsystemd0:amd64 (255.4-1ubuntu8.16, 255.4-1ubuntu8.17), ...
End-Date: 2026-08-11 09:48:52
Each entry records the date, the exact command line, and every package version before and after. The Commandline field is the valuable one: it tells you whether a human ran the change or whether automatic updates did it. Older logs are rotated and compressed alongside it, so zless /var/log/apt/history.log.1.gz reaches further back.
The :amd64 suffix on those package names is multiarch, Debian's support for installing packages for more than one architecture side by side. It matters when you need 32-bit libraries on a 64-bit machine, or when a dependency can only be satisfied by a specific architecture:
$ dpkg --print-architecture # the machine's native architecture
amd64
$ dpkg --print-foreign-architectures # extra architectures enabled, if any
Most servers have nothing in that second list, and then the suffix is just noise in the output. Once a foreign architecture is enabled, package name and version are no longer enough to identify a package and you will start seeing package:architecture in error messages.
6. Advanced Use Cases
6.1 Where Packages Come From
apt only knows about repositories you have configured. Historically that meant one file, /etc/apt/sources.list, with one repository per line. On Ubuntu 24.04 that file is now almost empty and says so:
$ cat /etc/apt/sources.list
# Ubuntu sources have moved to /etc/apt/sources.list.d/ubuntu.sources
The replacement is the deb822 format: one block of named fields per repository, which is far easier to read and to manage with configuration tooling.
$ cat /etc/apt/sources.list.d/ubuntu.sources
Types: deb
URIs: http://nl.archive.ubuntu.com/ubuntu/
Suites: noble noble-updates noble-backports
Components: main restricted universe multiverse
Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg
Types: deb
URIs: http://security.ubuntu.com/ubuntu/
Suites: noble-security
Components: main restricted universe multiverse
Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg
Both formats still work, and a real machine usually has a mixture, because third-party installers write whichever they were built for. The old one-line form packs the same information into a single line:
$ cat /etc/apt/sources.list.d/docker.list
deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu focal stable
The fields are worth knowing because they explain a lot of behaviour. Suites names the release and its update channels, so noble-security is where security fixes arrive. Components selects the sections: on Ubuntu, main is officially supported, universe is community-maintained, restricted and multiverse hold drivers and software with licence restrictions. If a package "does not exist", the usual cause is that its component is not enabled.
6.2 Signing Keys, and the apt-key Trap
apt does not trust a repository because you typed its address. Every repository is cryptographically signed, and apt verifies the signature against a key you have installed. This is the security boundary of the whole system: an attacker who can modify traffic to your mirror still cannot make you install their package.
The way you install those keys changed, and a great deal of published advice is now actively harmful. The old way was apt-key, which adds a key trusted for every repository on the system:
$ apt-key list
Warning: apt-key is deprecated. Manage keyring files in trusted.gpg.d instead (see apt-key(8)).
The problem is scope. A key added with apt-key add can sign packages claiming to be anything, including a replacement for your kernel or your SSH server. A third-party repository for a text editor should not be able to do that.
The modern pattern stores the key in its own file and binds it to one repository with Signed-By:
$ curl -fsSL https://example.com/key.gpg | sudo tee /etc/apt/keyrings/example.asc > /dev/null
$ echo "Types: deb
URIs: https://example.com/apt/
Suites: stable
Components: main
Signed-By: /etc/apt/keyrings/example.asc" | sudo tee /etc/apt/sources.list.d/example.sources
Now that key is trusted for that repository and nothing else. If you find instructions telling you to pipe a key into apt-key add, they predate 2022 and should be translated into the form above.
6.3 What Is Actually Inside a Package
Signatures matter more than they first appear, and the reason becomes obvious once you look inside a .deb. You can download one without installing it:
$ apt-get download tree
$ ls
tree_2.1.1-2ubuntu3.24.04.2_amd64.deb
A .deb is an archive with two halves: the files to install, and a control archive describing the package. dpkg-deb -I shows the second half:
$ dpkg-deb -I tree_2.1.1-2ubuntu3.24.04.2_amd64.deb
new Debian package, version 2.0.
size 47360 bytes: control archive=746 bytes.
595 bytes, 14 lines control
367 bytes, 6 lines md5sums
Package: tree
Version: 2.1.1-2ubuntu3.24.04.2
Architecture: amd64
Installed-Size: 108
Depends: libc6 (>= 2.38)
And dpkg-deb -c lists the files, with the paths they will occupy:
$ dpkg-deb -c tree_2.1.1-2ubuntu3.24.04.2_amd64.deb
drwxr-xr-x root/root 0 ./usr/bin/
-rwxr-xr-x root/root 85400 ./usr/bin/tree
That package is harmless. Now look at one that is not just files. The control archive of ca-certificates contains three executable scripts, marked with an asterisk:
$ dpkg-deb -I ca-certificates_20260601~24.04.1_all.deb
size 139430 bytes: control archive=16630 bytes.
9029 bytes, 163 lines * config #!/bin/sh
880 bytes, 21 lines control
13789 bytes, 138 lines md5sums
5817 bytes, 187 lines * postinst #!/bin/sh
1629 bytes, 65 lines * postrm #!/bin/sh
These are maintainer scripts, and there are four possible names: preinst and postinst run before and after the files are unpacked, prerm and postrm before and after removal. They are ordinary shell scripts, and dpkg runs them as root.
Installing a package is not extracting an archive. It is running someone else's shell scripts with full privileges on your machine. That is the entire reason apt refuses to install from an unsigned repository, and the reason a third-party repository is a genuine trust decision rather than a convenience.
Those scripts also explain a line everyone has seen scroll past and nobody reads. Debian packages can register triggers, which defer shared work until the end of a transaction rather than repeating it for every package:
Processing triggers for libc-bin (2.39-0ubuntu8.8) ...
Processing triggers for man-db (2.12.0-4build2) ...
Rebuilding the manual page index once after installing twenty packages is much faster than rebuilding it twenty times. When an install seems to pause at the end having apparently finished, this is usually what it is doing.
6.4 Which Version Will I Actually Get?
When several repositories offer the same package, apt picks by priority. apt policy shows the whole picture, and with a package name it answers the specific question:
$ apt-cache policy bash
bash:
Installed: 5.2.21-2ubuntu4
Candidate: 5.2.21-2ubuntu4
Version table:
*** 5.2.21-2ubuntu4 500
500 http://nl.archive.ubuntu.com/ubuntu noble/main amd64 Packages
100 /var/lib/dpkg/status
Installed is what you have, Candidate is what an upgrade would give you, and the numbers are priorities. The default is 500 for a normal repository and 100 for the already-installed version, which is why apt prefers a repository version over what is on disk but will not downgrade you for fun.
This command is the fastest way to answer "why is it installing that version?" and "is this package coming from the repository I think it is?", which matters when a third-party repository shadows a distribution package.
Those version strings are structured, not arbitrary, and reading them is a useful skill:
[epoch:]upstream_version[-debian_revision]
1:2.43.0-1ubuntu7.3
| | |
| | +-- packaging revision: changes to the packaging, not the software
| +---------- upstream version: what the software's own authors released
+------------- epoch: rarely present, and it overrides everything else
The epoch is the interesting one. Version comparison is normally mechanical, but occasionally upstream changes its numbering in a way that makes the new release sort lower than the old one. An epoch is a manual override that says "trust me, this is newer". Once a package has an epoch it keeps it forever, which is why git on Ubuntu is version 1:2.43.0-1ubuntu7.3 rather than 2.43.0-1ubuntu7.3.
When the default priorities are not what you want, you can change them. Pinning lives in /etc/apt/preferences.d/ and adjusts the priority of a repository or a package, which is how you use one package from a newer suite without dragging the whole system along:
$ cat /etc/apt/preferences.d/99-backports
Package: *
Pin: release a=noble-backports
Pin-Priority: 100
Pinning is powerful and easy to get subtly wrong, because the priority numbers carry specific meanings rather than being a simple ranking. Always check the result with apt-cache policy before trusting it, and prefer the smallest rule that solves your problem.
6.5 Recommends: The Reason Installs Are Bigger Than You Expect
Debian packages express three strengths of relationship. Depends is required. Recommends is "almost everybody wants this too", and apt installs it by default. Suggests is a hint, and apt ignores it.
Those are the three you meet daily, but the packaging format has more, and they are why dependency resolution is a solver rather than a checklist. Pre-Depends forces an ordering, requiring another package to be fully configured first. Conflicts and Breaks say two packages cannot coexist, or that this one breaks a version of another. Replaces lets a package take over files from another, and Provides lets several packages satisfy one generic name, which is how a system can offer a choice of mail server or Java runtime. apt has to find a set of versions that satisfies all of them at once.
$ apt-cache show git | grep -E '^(Recommends|Suggests):'
Recommends: ca-certificates, patch, less, ssh-client
Suggests: gettext-base, git-daemon-run | git-daemon-sysvinit, git-doc, git-email, ...
Recommends are the reason a small package can pull in a long list. The effect is easy to measure with -s (simulate), which shows what would happen without doing it:
$ apt-get install -s git | grep 'newly installed'
1 upgraded, 46 newly installed, 0 to remove and 2 not upgraded.
$ apt-get install -s --no-install-recommends git | grep 'newly installed'
0 upgraded, 23 newly installed, 0 to remove and 3 not upgraded.
Exactly half. On a container image or a small server that difference is worth having, which is why --no-install-recommends appears in almost every Dockerfile. On a desktop, leave recommends on: they are recommended for a reason, and turning them off system-wide produces software that quietly lacks features.
That -s flag deserves a place in your habits generally. It answers "what would this do?" for any apt command, with no risk at all.
6.6 Updating Without Being There
A server that only gets patched when someone remembers is a server that does not get patched. Debian and Ubuntu ship unattended-upgrades for this, and on Ubuntu it is usually installed and enabled already, which you can confirm from the history log:
$ grep -c 'unattended-upgrade' /var/log/apt/history.log
$ cat /etc/apt/apt.conf.d/50unattended-upgrades # what it is allowed to install
$ sudo unattended-upgrade --dry-run --debug # what it would do right now
The default policy installs security updates only, which is the right trade for most servers: real protection, minimal risk of a behaviour change breaking something at three in the morning. The setting worth deciding deliberately is Unattended-Upgrade::Automatic-Reboot, because a kernel security fix does nothing until the machine reboots, and an unplanned reboot is its own kind of outage.
7. Something Most Users Do Not Know
7.1 apt Knows Whether You Are a Human
Here is a warning that thousands of people have seen in a CI log and never once at their own terminal:
WARNING: apt does not have a stable CLI interface. Use with caution in scripts.
It looks like it appears at random. It does not. apt checks whether its output is going to a terminal or into a pipe, and only warns in the second case. You can prove it in two commands:
$ apt list --installed | grep -c 'stable CLI interface'
1 # output piped: warned
# same command with output going to the terminal: no warning at all
The warning is aimed precisely at the situation where it matters. When you type apt interactively, the wording of its output is your problem for the next three seconds. When something parses that output, the wording is a dependency, and the apt developers explicitly refuse to promise it will not change between releases.
That is the real difference between the two commands, and it is a promise rather than a feature list. apt-get has a stable interface that scripts may rely on; apt is free to change its output whenever a nicer format comes along. Hence: type apt, script apt-get.
The terminal check shows up elsewhere too. Since apt 1.9.11 the lock behaves differently depending on who is watching:
apt(8) now waits for the lock indefinitely if connected to a tty, or
for 120 seconds if not.
So an interactive apt waits patiently while unattended-upgrades finishes, and a scripted one gives up after two minutes rather than hanging a deployment forever.
7.2 Two Identical Servers, Different Updates
You patch two servers built from the same image on the same day. One upgrades a package, the other reports it kept back. Nothing is held, nothing is pinned, and the machines are otherwise identical. This is not a fault; it is phased updates working as designed.
Ubuntu rolls updates out gradually to catch regressions before they reach everyone. A package can carry a field saying what fraction of machines should take it yet:
Phased-Update-Percentage: 10
The clever part is how a machine decides whether it is in that ten percent, and the apt_preferences manual spells it out:
A system's eligibility to a phased update is determined by seeding
random number generator with the package source name, the version
number, and /etc/machine-id, and then calculating an integer in the
range [0, 100]. If this integer is larger than the
Phased-Update-Percentage, the version is pinned to 1, and thus held
back.
Your machine's identity is in the seed. That is why two servers disagree, and it is deliberate: the decision is stable, so a machine does not flip between offering and hiding an update on every run, but it is different per machine and per package, so the rollout spreads evenly.
It also means you can control it. To make a fleet phase together, give them the same APT::Machine-ID. To opt out of waiting, on a staging machine where you want tomorrow's updates today:
$ sudo apt-get -o APT::Get::Always-Include-Phased-Updates=true upgrade
Before you reach for that on production, remember what the delay is buying you: somebody else discovers the regression first.
7.3 The Accident That Changed apt
apt used to protect essential packages with a prompt. If a request would remove something the system cannot function without, it demanded you type a specific sentence:
You are about to do something potentially harmful.
To continue type in the phrase 'Yes, do as I say!'
In November 2021 a very widely watched video showed exactly what that prompt is worth. During a software installation challenge, the host from Linus Tech Tips attempted to install Steam on a Pop!_OS system. Due to an underlying dependency conflict, the package manager proposed a destructive solution: removing the entire desktop environment.
Despite a warning prompt requiring the exact phrase "Yes, do as I say!", the presenter entered the command without realizing the consequences. Upon rebooting, the graphical interface was gone, leaving only a bare text console. This high-profile incident proved that confirmation prompts often act as mere speed bumps, prompting developers to update the software to block the removal of essential system packages entirely.
apt changed as a result, and the release notes on your own disk record it without drama:
apt (2.3.12) unstable; urgency=medium
The solver will no longer try to remove Essential or Protected packages,
any dependency problem that would need such a solution will have to be
resolved manually.
The "Yes, do as I say" prompt for removing essential packages has been
replaced by an error message.
Thank you to Linus Tech Tips and System76 for bringing this issue
to our attention.
The design lesson is worth more than the anecdote. A confirmation prompt that a determined user can get past is not a safety mechanism, it is a speed bump with paperwork. The fix was to make the dangerous operation impossible by default and require a deliberate command-line option instead. You can read that entry yourself with zless /usr/share/doc/apt/NEWS.Debian.gz, which is worth doing on any system: it is a short list of exactly the behaviour changes most likely to surprise you.
7.4 Knowing Where apt Stops
apt manages software your distribution packages. A lot of what runs on a modern server is not that.
| Need | Use | Why |
|---|---|---|
Install a downloaded .deb |
apt install ./file.deb |
Note: the ./ makes apt resolve the dependencies, which plain dpkg -i will not |
| Find which package owns a file | dpkg -S |
Answers "what installed /usr/bin/x?" instantly |
| Newer software than your release ships | backports, a vendor repository, or a container | A stable release deliberately freezes versions; do not fight it in place |
| Language libraries | pip, npm, composer |
Per-project versions that apt cannot express; keep them out of system directories |
| Untangle a hopeless conflict | aptitude |
Offers alternative solutions interactively when apt just refuses |
| The same setup on many servers | Ansible, or a container image | Records what should be installed instead of relying on what someone typed once |
I installed .deb packages for years using sudo dpkg -i package.deb While writing this article, I learned about sudo apt install ./package.deb. It does the same local package installation, but with one important advantage: apt can fetch and install missing dependencies automatically.
8. Best Practices
- Always
updatebeforeinstallorupgrade. Acting on a stale catalogue produces both "package not found" and downloads of versions that no longer exist. - Read the summary line before pressing Enter. Specifically the "to remove" count. That number is where accidents live.
- Type
apt, scriptapt-get. Only one of them promises its output will look the same next year. - Simulate anything you are unsure about.
-s(or--simulate, or--dry-run) works on bothaptandapt-get, prints the full plan, and changes nothing. - Enable unattended security upgrades on every server. Then decide the reboot policy deliberately rather than leaving it at the default.
- Bind third-party keys to their own repository with
Signed-Byand a file in/etc/apt/keyrings/. Neverapt-key add. - Check
apt-mark showholdwhen a machine stops updating. A hold set months ago during an incident is a classic cause. - Use
--no-install-recommendsfor containers and minimal servers, and leave recommends enabled on desktops. - Read
autoremovelists before agreeing. "Nothing depends on it" is not the same as "you do not need it". - Reach for the documentation. The manual pages are unusually good, and
NEWS.Debian.gztells you what changed in behaviour.
$ man apt # the friendly command
$ man apt-get # the scripting interface
$ man sources.list # repository configuration, both formats
$ man apt_preferences # priorities, pinning, phased updates
$ man apt-patterns # the search syntax, since apt 2.0
$ zless /usr/share/doc/apt/NEWS.Debian.gz # behaviour changes worth knowing
Back to top9. Common Mistakes
9.1 Common Myths
| Myth | Reality |
|---|---|
"apt update updates my software." |
It updates the catalogue only. apt upgrade updates software. |
"apt is just a shorter alias for apt-get." |
Separate binaries with different behaviour. Only apt-get promises a stable interface for scripts. |
| "'Unable to locate package' means it is not packaged." | It usually means no apt update, or the component (universe) is not enabled. |
"apt remove uninstalls a package completely." |
Configuration files stay, and dpkg lists it as rc. apt purge removes those too. |
| "Kept-back packages mean something is broken." | Usually a hold, or Ubuntu's phased rollout deciding it is not this machine's turn yet. |
"apt-key add is how you trust a repository." |
Deprecated, and it trusts that key for every repository. Use Signed-By with a file in /etc/apt/keyrings/. |
9.2 Traps to Avoid
- Running
autoremovewithout reading the list. It happily removes a kernel, a driver, or the manually built tool apt believes was a dependency. dpkg -ifor a downloaded package. It cannot fetch dependencies and leaves the package half-configured. Useapt install ./file.deb, orapt --fix-broken installto recover.- Adding a repository built for another release. A
focalrepository on anoblemachine will install packages linked against the wrong libraries, and the damage shows up later. - Killing apt mid-transaction. You get a lock file and a half-configured package.
sudo dpkg --configure -ais usually the way back; deleting lock files by hand while apt is running is not. - Assuming
upgradecovers everything. It never removes packages, so some upgrades wait forever. Check periodically withfull-upgrade --dry-run. - Forgetting
/boot. Kernel packages accumulate and a small/bootpartition fills, after which upgrades fail confusingly. Modern apt removes unused kernels onfull-upgrade, but only that command. - Piping
aptoutput into a script. The wording is not stable, and apt warns you about exactly this. - Mixing pip or npm into system directories. Installing over files that apt owns produces breakage that no apt command can explain. Use virtual environments or per-project installs.
10. Summary
apt looks like two commands you memorise on your first day. It is really the dependency solver, downloader and trust system that everything else on a Debian or Ubuntu machine is built on.
- APT means Advanced Package Tool. It solves dependencies and downloads packages;
dpkgunderneath does the actual installing. apt updaterefreshes the catalogue,apt upgradeinstalls newer versions, andfull-upgradeis the same but may remove packages to get there.- It began as apt 0.0.1 on 31 March 1998, "released only on IRC", and shipped in Debian 2.1 in 1999. The
aptcommand itself arrived in April 2014, "10000b years in the making". - "Unable to locate package" nearly always means a missing
apt updateor a disabled component, not a missing package. removeleaves configuration files behind and dpkg marks the packagerc;purgeremoves those too.- Recommends are installed by default and explain large installs.
--no-install-recommendscut agitinstall from 46 packages to 23. apt-mark holdpins a package and produces "kept back";apt-mark showholdis the first thing to check when a machine stops updating.- Repositories live in
/etc/apt/sources.list.d/, increasingly in the deb822.sourcesformat, and each should be bound to its own key withSigned-By. - apt only prints its "no stable CLI interface" warning when output is not a terminal. Type
apt, scriptapt-get. - Ubuntu's phased updates seed a decision with the package, the version and
/etc/machine-id, which is why two identical servers legitimately get different updates. - Since apt 2.3.12 the solver refuses to remove essential packages at all, after a public accident showed that a confirmation prompt is not a safety mechanism.
- Installing a package runs its
preinstandpostinstscripts as root, which is why repository signatures andSigned-Byscoping are the real security boundary. - Version strings are
[epoch:]upstream-revision; the rare leading1:is an epoch that overrides normal version ordering, and pinning in/etc/apt/preferences.d/overrides repository priorities. - When in doubt, simulate:
apt-get install -sandapt upgrade --dry-runprint the entire plan and change nothing.
This is the quick reference worth keeping:
apt update refresh the catalogue (installs nothing)
apt upgrade install newer versions, never removing anything
apt full-upgrade as above, but may remove packages to succeed
apt install PKG install a package
apt install ./file.deb install a downloaded .deb WITH dependencies
apt remove PKG uninstall, keeping configuration files
apt purge PKG uninstall, including configuration files
apt autoremove delete orphaned dependencies (read the list!)
apt search TEXT search names and descriptions
apt show PKG version, size, dependencies, origin
apt list -a PKG every version apt knows about
apt list --upgradable what an upgrade would change
apt-cache policy PKG which version, from which repository, and why
apt-get install -s PKG simulate: print the plan, change nothing
apt-get download PKG fetch the .deb without installing it
apt-config dump apt's effective configuration
dpkg-deb -I file.deb a package's control data and maintainer scripts
dpkg-deb -c file.deb the files a package would install
apt-mark hold PKG freeze a package at its current version
apt-mark showhold list frozen packages
apt-mark manual PKG "I want this", protect it from autoremove
dpkg -l PKG package state: ii installed, rc removed-but-config
dpkg -S /path/to/file which package owns this file
dpkg --configure -a recover from an interrupted transaction
Package management is where most server trouble either starts or gets fixed: the upgrade that was never applied, the third-party repository nobody documented, the disk that filled with old kernels.
Back to top

Peter is a Joomla specialist and a Linux admin for fast, secure and scalable websites.












