Packages and updates
apt, signed repositories and updates.
Almost all software on an Ubuntu server arrives as packages from repositories that Ubuntu signs. This lesson shows where those repositories are configured, how apt refreshes its lists and applies updates, how to find out what a package contains and which package owns a file, and how to add a vendor's repository without handing that vendor more trust than it needs. It finishes with the same jobs on RHEL, where dnf and rpm do them. The package manager decides whose code your server will run, which is why it deserves the same care as the SSH configuration.
Where packages come from
A package is an archive of files plus a description: its name, version, the other packages it depends on, and scripts to run when it is installed or removed. dpkg installs single package files. apt sits on top of it: it downloads packages from repositories, works out dependencies, and hands the files to dpkg. On Ubuntu 26.04 the repositories are listed in files ending in .sources under /etc/apt/sources.list.d/; the old /etc/apt/sources.list now holds only a comment pointing there.
The file is written by cloud-init when a cloud image first boots and starts with a long comment, left out here. Each block of Field: value lines, in the format called deb822, describes one source. Types: deb means binary packages. URIs is the server. Suites are sections of the release: resolute is Ubuntu 26.04 as released, resolute-updates carries bug fixes, resolute-backports newer versions of some programs, and resolute-security, from security.ubuntu.com, security fixes. Components split the archive by who maintains it: main is supported by Canonical, while universe is community-maintained, and the file's own comments say Canonical's security maintenance of it comes with an Ubuntu Pro subscription. Signed-By names the keyring whose keys may sign this repository, and no other.
Those signatures protect you from a tampered mirror or network. When apt refreshes, it downloads each suite's InRelease file, a signed list of the repository's index files with their checksums, and each index lists a checksum for every package. A package is installed only if every link checks out.
Older guides add keys with apt-key add, which put every key in one store trusted for all repositories. apt 3 removed that command, so it does not exist on Ubuntu 26.04; the modern way is a keyring file named in Signed-By, as shown later in this lesson.
Refreshing the lists, then upgrading
apt update and apt upgrade do different jobs. update downloads fresh copies of the repository indexes, so apt knows which versions exist, and installs nothing. upgrade installs newer versions of packages you already have. Run them in that order.
Hit means an index had not changed since the last refresh; Get means it had, and was downloaded again. The last line counts the packages with a newer version available, and apt list --upgradable names them: package, the suite the new version comes from, the new version, and the installed one. All six come from resolute-updates, the bug-fix suite. Before upgrading, look at what apt plans to do. --assume-no prints the plan and answers no to the question:
apt would upgrade nothing today. Ubuntu phases bug-fix updates: a new version goes to a small share of machines first and to more as long as error reports stay low. Each machine works out its own place from its machine ID, the package and the version. apt policy shows the new ubuntu-server released to 80% of machines, and this one is in the other 20% for now; *** marks the installed version. Security updates are never phased. Without --assume-no, sudo apt upgrade asks Continue? [Y/n] before changing anything.
You also do not have to remember to install security fixes. Ubuntu Server ships unattended-upgrades installed and switched on: a daily timer refreshes the lists and installs updates from the security suite. Verify it rather than installing it:
Both units are active and both settings are "1", which means daily. What the job may install, how services pick up fixed libraries, and when to reboot are decisions for the lesson "Patching and update strategy" in the Linux hardening course. Ubuntu has a second package system, snap, whose packages snapd refreshes on its own schedule; a server install has none (snap list says No snaps are installed yet).
One package, from search to removal
apt search --names-only WORD finds packages by name. apt show describes one, and apt policy says which versions exist and where each comes from. The example is ncdu, a disk-usage browser.
Section: universe/admin places it in the community-maintained component. Depends lists what it needs, and APT-Sources shows which repository the candidate comes from. In apt policy, Installed: (none) means it is not installed, Candidate is the version apt install would choose, and 500 is the default priority of a repository.
apt shows its plan, downloads the package, and dpkg unpacks and configures it. The lines left out are messages from debconf, the tool that asks packages' configuration questions, about having no terminal to ask on. The last lines come from needrestart, which Ubuntu runs after every apt install or upgrade to find running services that still use a library the upgrade replaced. Restarting services... means what it says: when apt runs it, Ubuntu sets its mode to automatic (the comments in /etc/needrestart/needrestart.conf say so), and it restarts such services without asking, except the few it defers. Here the only services still using replaced libraries were the three it defers, so nothing was restarted, but after an update of a library such as OpenSSL, the same sudo apt upgrade restarts your database or web server in the middle of the day. On production servers, plan updates for a quiet time; a file in /etc/needrestart/conf.d/ containing $nrconf{restart} = 'l'; makes needrestart only list what needs a restart. "Patching and update strategy" in the hardening course covers this policy.
Now ask dpkg what it installed, and turn the question around: which package owns a file? For the second question, first copy a script into /usr/local/bin by hand, as happens on servers that have been looked after for years (sudo tee writes what it reads to a root-owned file and also prints it):
Now list what ncdu installed, then ask which package owns a system program and the script:
dpkg -L lists every path the package installed. dpkg -S searches the same records in reverse: /usr/bin/ssh belongs to openssh-client. The script belongs to no package, which is normal for /usr/local but worth knowing about on a server you inherit: nothing will update it, and nobody may know where it came from. (The last command removes the practice script again.)
dpkg also stored a checksum of every file it installed. dpkg --verify compares the files on disk with those checksums and prints only the ones that differ. For this example, root appended a byte to /usr/bin/ncdu:
The 5 in ??5?????? means the checksum no longer matches; the question marks are checks dpkg does not perform. The exit status is 0 either way, so read the output. dpkg(1) calls this an integrity check and not a security verification, because root can also rewrite dpkg's own records; the hardening course's lesson on file integrity monitoring with AIDE covers a baseline kept out of an intruder's reach.
/var/log/apt/history.log records every apt run: the command, who ran it through sudo, and what changed. To remove a package, apt remove keeps its configuration files and apt purge deletes them too, which the * after the name marks. -y answers yes to the confirmation question.
Adding a vendor's repository
When software is not in Ubuntu's archive, many vendors publish their own apt repository. Adding one means trusting its signing key for everything that repository serves, now and in future updates, so tie the key to that repository alone. Docker's documented procedure does exactly that, and it is a good pattern to copy: a deb822 file whose Signed-By names a key file in /etc/apt/keyrings, the directory for keys managed by the administrator. The command uses three pieces of shell syntax not met before. <<EOF (a here-document) feeds the lines that follow to tee as its input, up to the line EOF. Inside $( ), . /etc/os-release reads that file's variables into the shell, and ${UBUNTU_CODENAME:-$VERSION_CODENAME} uses UBUNTU_CODENAME, or VERSION_CODENAME if the first is empty, which gives the release's code name; the second $( ) prints the machine's architecture.
The lab machine is arm64; most servers print amd64 there. The key file does not exist yet, so the next apt update shows what a repository without a trusted key looks like:
apt fetched Docker's InRelease, could not verify its signature (NO_PUBKEY names the key it needed), refused the repository, and exited with status 100. Ubuntu's own repositories were unaffected. Never silence this with [trusted=yes] in the sources file, which turns the check off. Fetch the vendor's key into the file that Signed-By names, as Docker's instructions do, and refresh again:
The repository now verifies, and apt policy shows Docker's versions coming from Docker's server. Its key can vouch only for that repository. Removing the .sources file and the key file, then running sudo apt update, removes the source again. A vendor's signed repository is also the answer to install instructions that pipe a script into a shell: updates then arrive through the same checked path as Ubuntu's own.
On RHEL: dnf and rpm
RHEL, and rebuilds such as Rocky Linux, install .rpm packages with dnf, and rpm plays the part of dpkg. Repositories are defined in /etc/yum.repos.d/*.repo, and each carries gpgcheck=1 and the key that must have signed its packages:
The commands map almost one to one. Every RHEL command in this table was run on the Rocky Linux lab machine (installs and removals with -y, the upgrade with --assumeno):
Task Ubuntu 26.04 (apt, dpkg) RHEL 10 (dnf, rpm)Refresh package lists sudo apt update (dnf refreshes when its cache expires)List pending updates apt list --upgradable dnf check-updateUpgrade installed packages sudo apt upgrade sudo dnf upgradeDescribe a package apt show NAME dnf info NAMEInstall, remove sudo apt install / purge NAME sudo dnf install / remove NAMEWhich package owns a file dpkg -S PATH rpm -qf PATHFiles in a package dpkg -L NAME rpm -ql NAMECompare files with records dpkg --verify NAME rpm -V NAMEWhat changed, and when /var/log/apt/history.log sudo dnf history listRepository definitions /etc/apt/sources.list.d/ /etc/yum.repos.d/Automatic security updates unattended-upgrades (on) dnf-automatic (not installed)
dnf check-update exits with status 100 when updates are waiting, which scripts can test. Here they are two kernel tool packages that match a newer kernel installed on this machine. rpm -qf answers the same question as dpkg -S, here for the same hand-copied script on the Rocky machine. The last line is the biggest difference in defaults: RHEL installs nothing that applies updates automatically, and setting up dnf-automatic is part of the patching lesson in the hardening course.
Try this
Pick a program you rely on, such as rsync. Find the package that owns it with dpkg -S /usr/bin/rsync (it prints rsync: /usr/bin/rsync), then run apt policy rsync and read which version is installed and which suites offer it: on the lab machine the installed version, marked ***, is offered by both resolute-updates and resolute-security, so it arrived as a security update. Then run sudo apt update and sudo apt upgrade --assume-no on your own machine and explain, for every package listed, why apt would or would not install it today.
Takeaway
Install from signed repositories, with each third-party key tied to its own repository through Signed-By, and read apt's plan before you let it change anything. When a file on a server matters, ask the package database who owns it.