The Half of Patching That Happens After the Patch is Installed

ByLinuxtastic

September 3, 2026

A machine has just taken a real OpenSSL security upgrade. Five processes are still running the deleted library, and the tool that is supposed to say so, run exactly as the package’s own APT hook runs it, prints this:

Setting up libssl3t64:amd64 (3.0.13-0ubuntu3.15) ...

No services need to be restarted.
No containers need to be restarted.
No user sessions are running outdated binaries.

It had found all five. It printed none of them, because the report it produces is a list of names, service names and package names, and it had not managed to name any of them. One verbosity level up, everything it found is on the screen. This site’s own Hardening a Public-Facing Service says that a patched library does not protect a process still running the old one, and to install needrestart so you find out; the missing clause is that needrestart is the thing that does not tell you. This page is about the half of patching that starts once the file on disk is already correct. Getting the file correct is The Life of a Package‘s subject; why a container is not patched by its host is Containers, All the Way Down‘s. What happens next, which processes picked the fix up, which ones nothing will ever restart, and how you prove any of it to somebody who does not believe you, is where the time goes. Every number here came off one machine: Ubuntu 24.04.4, kernel 6.18.44, apt 2.8.3, unattended-upgrades 2.9.1, needrestart 3.6, dnf 4.14. The mechanism is Debian- and Ubuntu-shaped; the RPM side gets its own section, and everything about Livepatch is Canonical’s documentation rather than a measurement.

The seven stages, from an advisory to a machine you can vouch for. Every section below is named after one.

  1. The version number that was designed not to move.
  2. What “security” means to your machine — which is not what it means to you.
  3. The automation that is already running, and what it is already not doing.
  4. The process still running the old code — and the tool that found it and said nothing.
  5. What a restart restarts, and the code on the box that no package owns.
  6. The reboot, and what livepatching actually removes.
  7. Proving it — to a scanner, an auditor, or yourself at three in the morning.

The diagnostic hinge: the kernel knows which processes are running deleted code. It does not know what to call them. Every restart tool is a guess at the name — and the ones it cannot name, it does not mention. So do not ask the tool what needs restarting. Ask it what it found, and then ask it what it is willing to say:

sudo needrestart -v -b 2>&1 | grep -E 'uses (deleted|obsolete)|^NEEDRESTART-'

Two lists come back. The uses deleted lines are what the kernel told it. The NEEDRESTART-SVC and NEEDRESTART-PID lines are what it will act on. Read the difference between them:

  • A uses deleted line with a matching SVC or PID line. The tool has it, and will restart it or already has. Stage 5.
  • A uses deleted line with no matching SVC or PID line. It found the process and dropped it: no package owns the binary, no unit owns the process, or the unit is on the tool’s own do-not-restart list. Nothing will ever restart this. You will. Stage 4 — and this branch is the reason the page exists.
  • No uses deleted lines, and the installed version is the current one. The process never mapped the file: interpreted code, a data file read once at startup, or a container running the image’s own copy. Check readlink /proc/PID/root before you believe the path. Stage 5.
  • No uses deleted lines, and the installed version is not the security version. The patch never landed at all. Stages 2–3.
  • NEEDRESTART-KSTA: 0. Not “no reboot needed” — “I could not find your kernel”. The reboot question is unanswered. Stage 6.

What the hinge gives up, said out loud. It is shaped for Debian and Ubuntu. The RPM tools ask a time-based question rather than a deletion-based one, so the two lists do not line up the same way — stage 4 gives you the parallel command and is explicit that the test underneath it is a different test.

Stage 1: The version number that was designed not to move

Distributions do not ship you the upstream fix. They take the upstream patch, apply it to the version they already shipped, and change as little else as possible — because the version you are running is the version the rest of your software was tested against. That is backporting, and it means the number your software prints is deliberately uninformative.

$ openssl version
OpenSSL 3.0.13 30 Jan 2024

$ zcat /usr/share/doc/libssl3t64/changelog.Debian.gz | grep -c '^openssl (3\.0\.13-'
15

Fifteen separate Ubuntu uploads of upstream 3.0.13, and all fifteen print that same line. A machine patched this morning and a machine last patched in January 2024 are indistinguishable by the command everyone runs. The only field that moves is one you have to ask for:

$ openssl version -a | grep 'built on'
built on: Tue Aug 18 12:10:36 2026 UTC

This is the fact that makes the rest of the page necessary, and its consequence is met before it is understood: any scanner keyed to upstream version strings calls a fully patched distribution machine vulnerable. The canonical example is on this very box, needrestart 3.6, where upstream fixed CVE-2024-48990 in 3.8 and the changelog says the fix has been here all along:

needrestart (3.6-7ubuntu4.3) noble-security; urgency=medium
  * SECURITY UPDATE: incorrect usage of PYTHONPATH environment variable
    - debian/patches/CVE-2024-48990.patch: chdir to a clean directory
    - CVE-2024-48990

Proving that mechanically is stage 7’s job, because the obvious way of doing it is broken on precisely the package you would check first. Two things about CVE numbers, and then this page stops talking about them. The CVSS score is not the number your distribution used: Ubuntu, Debian and Red Hat each assign their own priority, and that priority decides whether a fix is backported, how quickly, and on Ubuntu whether Livepatch carries it. And a CVE with no score at all is now normal. NIST changed NVD policy on 15 April 2026 so that enrichment is risk-prioritised rather than universal; anything that matches vulnerabilities by NVD’s CPE strings, which is most scanners, is now matching against a deliberately incomplete set.

Stage 2: What “security” means to your machine

Every guide on earth, this site’s included, says to restrict automatic updates to security updates, and almost nobody knows what the machine does with that. “Security” is not a property of a fix. It is an Origin, Suite and Label triple in a repository’s Release file, matched as a string. A package is a security update if it came from a pocket whose name matches, and for no other reason.

On Ubuntu that pocket is a copy, not a superset, and the arithmetic of it explains something almost every reader has seen. Measured across all 184 upgradable packages on this machine:

upgradable total:                            184
candidate in BOTH -security and -updates:    162
candidate in -security only:                   0
candidate in -updates only:                   22

Split those 22 by whether any newer version exists in -security at all, and the whole thing falls out:

A) nothing newer in -security  -> never installed automatically:   9
   procps, libproc2-0, libapparmor1, libinput10, libxmlb2,
   xserver-common, xvfb, libpfm4, libinput-bin

B) a LOWER version exists in -security -> that one is installed,
   and the package stays "upgradable" forever:                    13
   libkrb5-3        installed …2.6   security …2.7   updates …2.8
   python3.12       installed …0.13  security …0.15  updates …0.16
   linux-libc-dev   installed 6.8.0-111  security 6.8.0-138  updates 6.8.0-139

This is why apt list --upgradable never reaches zero on a machine that patches itself every night. Thirteen packages are being held one upload behind on purpose, nine more will never be offered, and the count is correct. It is a permanent, benign number that describes the policy working as designed.

Acting on that count is the most dangerous single change in this subject. Adding -updates to the allowed origins to make the number go to zero converts a machine that installs security fixes unattended into one that installs everything unattended, overnight, with no one watching. The alert clears. The risk moves.

One more trap in the same file, because it is silent: Debian and Ubuntu use different keys. Ubuntu ships Unattended-Upgrade::Allowed-Origins matching on ${distro_codename}-security; Debian ships Unattended-Upgrade::Origins-Pattern matching on codename= and label=. An Allowed-Origins block pasted onto a Debian machine is not an error and is not applied. And an Origins-Pattern block without #clear at the top is added to the stock list rather than replacing it.

Verify: apt-config dump | grep -iE 'Unattended-Upgrade::(Allowed|Origins)' prints what your machine is actually matching on, after every file in /etc/apt/apt.conf.d/ has had its say. That is the only place the answer exists.

Stage 3: The automation that is already running

On an Ubuntu cloud image, the first two commands in most hardening guides, this site’s included, are no-ops. unattended-upgrades is a hard dependency of the base image, so it is already installed and already enabled, and dpkg-reconfigure --priority=low unattended-upgrades asks one boolean question and configures nothing else. The reader believes they turned something on. They confirmed a default.

The RPM side ships as the exact mirror image, and this one is not benign:

# /etc/dnf/automatic.conf, as shipped
upgrade_type = default      # everything, not just security
apply_updates = no          # download only; install nothing, ever

unattended-upgrades ships security-only and enabled; dnf-automatic ships everything-but-installs-nothing and disabled. Enable the timer without touching the file and the machine downloads packages nightly and installs none of them, exiting 0 every time. Flip only apply_updates and you have enabled unattended installation of the entire distribution. Both keys, or neither.

If you rely on the security filter there, dnf5 issue #2754 is worth knowing: on Fedora 44, dnf5 upgrade --security did not install kernel security updates that dnf5 advisory list --security listed, so a machine running dnf-automatic with upgrade_type = security was not getting kernel fixes. (Read from the issue tracker; not reproduced here.)

The question worth asking is not which tool is better. It is what is going to run on your machine tonight, and there is a command for that rather than a table:

systemctl list-timers --all | grep -Ei 'apt-daily|unattended|dnf-automatic'
apt-config dump | grep -iE 'APT::Periodic|Unattended-Upgrade::Automatic'

One detail from that second command surprises people: APT::Periodic::Unattended-Upgrade "1" is not a boolean. It is an interval in days. "7" is legal and means weekly; "always" is legal too. A machine set to "0" has it switched off, which looks identical to a machine that never configured it.

Verify: the two commands above should show a timer with a NEXT time in the future and a non-zero period. Either one alone proves nothing — a timer that fires nightly into a configuration that installs nothing is the failure that survives longest in this subject, and it is invisible from either half.

Stage 4: The process still running the old code

A package upgrade replaces a file. It does not touch the copy already mapped into a running process, which keeps the old inode, deleted from the directory but alive, until it execs or exits. Everyone knows this. What almost nobody has looked at is what the tool that is supposed to tell you does with the knowledge. It finds them. Then, if it cannot work out a name to report them under, it discards them silently. Here is the full path, from its own debug output, for one process holding a deleted library:

[main] #2715 uses deleted /home/claude/work/src2/libvuln.so.1
[main] #2715 running /etc/needrestart/hook.d/10-dpkg
dpkg-query: no path found matching pattern .../holder
[main] #2715 running /etc/needrestart/hook.d/10-dpkg no perfect hit found 0 pids -1
[main] #2715 running /etc/needrestart/hook.d/20-rpm ... no perfect hit
[main] #2715 running /etc/needrestart/hook.d/90-none ... no perfect hit

Three hooks tried, three failures, and then nothing: no NEEDRESTART-PID line, no NEEDRESTART-SVC line, and at the verbosity the APT hook uses, “No services need to be restarted.” The evidence exists at exactly one place in the source and is gated exactly once — print STDERR "…#$pid uses deleted $path\n" if($nrconf{verbosity} > 1); — which is why the hinge at the top of this page is a verbosity flag and a grep.

On a real systemd host it does better than that trace suggests: it reads /proc/PID/cgroup, extracts a .service name and falls back to systemctl status, so a systemd-managed unpackaged binary is found. Three things still are not, and they are not exotic:

  • Anything whose cgroup read fails is skipped outright — next unless (defined($unit)); — with no fallback to the hooks that would have tried.
  • Anything in a user session — a tmux pane, a screen, a stray nohup — is diverted into “user sessions are running outdated binaries”, a heading that reports and never restarts.
  • Anything in a container’s cgroup is handed to a container backend, and on this machine every one of those backends failed to load at all.

The deny-list, the mtime check, and the automatic restart

It will not restart the daemons you would most want restarted. The shipped configuration carries a deny-list of about forty patterns, and it is a roll-call of exactly the long-lived processes that map OpenSSL and glibc:

$nrconf{override_rc} = {
    qr(^dbus) => 0,          qr(^network) => 0,   qr(^NetworkManager) => 0,
    qr(^wpa_supplicant) => 0, qr(^openvpn) => 0,  qr(^docker) => 0,
    qr(^libvirt) => 0,       qr(^systemd-logind) => 0,
    qr(^getty@.+\.service) => 0,  qr(^user@\d+\.service) => 0,
    ...
};

Declining to restart dbus automatically is defensible. Declining without saying so, in a tool whose entire output is a statement about what needs restarting, is the first defect wearing a different hat.

Its support for interpreted languages is an mtime check on one file. A running Python process whose imported module has been patched underneath it produces nothing at all, because the tool looks at the entry script’s modification time and nothing else. So it is false-negative on every imported module, and false-positive on any rsync without --times or any restore from backup.

And on Ubuntu it restarts services automatically by default. The APT hook runs it with -m u, which sets restart mode to automatic; every apt transaction since 22.04 has been restarting services on your behalf, minus the deny-list. That is worth knowing in both directions — it is why patching often does work without you, and it is why an upgrade occasionally bounces something you were using.

The Red Hat tool asks a different question

Not a different implementation of the same test; a different test. dnf needs-restarting compares the install time of the package owning each mapped file against the process’s start time. Deletion does not come into it; RPM renames the old file with a hexadecimal suffix before unlinking, so a stale mapping there reads /usr/lib64/libssl.so.3;68b7f2a1 (deleted).

The time-based test is in one way better: it catches a file replaced in place, with no unlink and no deleted marker, which the Debian-side tools cannot see at all. And it inherits the identical blind spot by completely different code — if pkg is None: continue. Both families drop every mapped file that no package owns, and neither says how many it dropped.

sudo dnf needs-restarting        # processes started before their packages
sudo dnf needs-restarting -s     # the same, grouped by systemd service

Verify: run the hinge command and count. If the number of uses deleted lines is larger than the number of NEEDRESTART-SVC and NEEDRESTART-PID lines, the difference is a list of processes nothing on the machine intends to restart, and you have just found the work.

Stage 5: What a restart restarts

systemctl restart replaces the processes in a unit’s cgroup. That is all it promises, and four common things are not covered by it.

  • Anything a supervisor other than systemd started. The unit restarts; the supervisor’s children are the supervisor’s business.
  • Anything running in a container. Restarting the container restarts the process against the image’s copy of the library, which the host’s patch never touched. Containers, All the Way Down has the mechanism; the operational half is that a rebuild is the patch and a restart is not.
  • Anything holding data rather than code — a CA bundle, a timezone table, a config parsed once at startup. No mapping, no deleted marker, nothing for any tool to find, and a service that will keep trusting a revoked certificate until something makes it read the file again.
  • Your own shell. The one you are typing in mapped libc when you logged in. It is on the list, and it is the process people are most surprised by.

Behind all four sits a larger question, and it has a command. How much of the code running on this machine does the package manager not know about?

# every mapped file on this box that no package owns
sudo awk '$6 ~ /^\// {print $6}' /proc/[0-9]*/maps | sort -u |
  while read f; do dpkg -S "$f" >/dev/null 2>&1 || echo "$f"; done

That list is the part of the machine apt cannot patch, however diligent your automation: the vendor binary in /opt, anything installed by pip, npm, cargo or go install, Snap and Flatpak payloads, container layers. Every entry on it is something that will never appear in apt list --upgradable no matter how long you wait.

Verify: after restarting what you believe you needed to, run the hinge command again. It should print no uses deleted lines for the paths you acted on. If a line survives a restart, the thing you restarted was not the thing holding the file.

Stage 6: The reboot, and what livepatching removes

The two standard ways of asking whether you need a reboot are both weaker than they look. /var/run/reboot-required is written by maintainer scripts, through a helper that ships in update-notifier-common, and each caller guards on the helper being present. That package is absent from Ubuntu cloud images and from Debian entirely, so on a great many servers the file is never created and its absence means nothing. Where it does exist, /var/run/reboot-required.pkgs names which packages asked, which is the more useful file and the one nobody mentions.

dnf needs-restarting -r is ten package names. Not a mechanism — a hard-coded list, compared against boot time:

NEED_REBOOT = ['kernel', 'kernel-core', 'kernel-rt', 'glibc',
               'linux-firmware', 'systemd', 'dbus', 'dbus-broker',
               'dbus-daemon', 'microcode_ctl']

openssl is not on it. Neither is openssh, zlib, libxml2, sudo or libcurl. Exit 0 is a true statement about ten packages and silent about the other four thousand. The check that always works is comparing the running kernel with the newest installed one: uname -r against /boot/vmlinuz-*.

Which brings up livepatching, and the honest framing is not the marketing one. Livepatch is a filter with four teeth. Canonical’s own documentation gives three: patches are provided “exclusively for Canonical-released kernels”, only for issues assigned a CVE, and only those “rated as high or critical priority”; the fourth is that patches are created for a kernel “for up to 9–13 months from its release date”, after which they stop, silently. Asked directly whether the service removes the need to reboot, the same documentation answers: “That is not the case.”

And it interacts with the previous point in a way worth stating plainly. Ubuntu’s kernel hook exits early when Livepatch reports that no upgrade is required, so the reboot flag is not written on a livepatched machine. Livepatching does not remove the reboot. It removes the notification of the reboot.

Verify: canonical-livepatch status --verbose on Ubuntu, or kpatch list on the Red Hat side, tells you whether anything is actually being livepatched and which kernel it is patching. (Neither could be run on the machine these numbers came from; both are the vendor’s documented interface.)

Stage 7: Proving it

The machine is fixed. Now somebody asks you to demonstrate it: a scanner, an auditor, a customer’s questionnaire, or you at three in the morning. The answer is local, offline, and in the changelog: a distribution that backports a fix records the CVE in the package’s changelog.Debian.gz. The obvious command for that is broken on the most security-relevant package on the machine:

$ zgrep -c CVE-2026-28387 /usr/share/doc/openssl/changelog.Debian.gz
gzip: /usr/share/doc/openssl/changelog.Debian.gz: No such file or directory
0

Zero hits and exit 0, from a dangling symlink. It points at ../libssl3/changelog.Debian.gz; the binary package was renamed libssl3 → libssl3t64 in the 64-bit time_t transition, and the old docdir was left behind empty. Every “check the changelog” recipe on the internet returns a clean, confident nothing here.

The working form resolves the file to its binary package, then to its source package, then looks in the docdir of any installed binary from that source:

cve=$1; file=$2
bin=$(dpkg -S "$(readlink -f "$file")" | cut -d: -f1)
src=$(dpkg-query -W -f='${source:Package}\n' "$bin")
for p in $(dpkg-query -W -f='${binary:Package} ${source:Package}\n' |
           awk -v s="$src" '$2==s {print $1}'); do
  cl=/usr/share/doc/$p/changelog.Debian.gz
  [ -s "$cl" ] && zgrep -q "$cve" "$cl" && { echo "FIXED — named in $cl"; exit 0; }
done
echo "NOT named — which is not the same as vulnerable"

Run against the two examples from stage 1, that returns FIXED for OpenSSL and for needrestart — the latter being the whole backporting story in one line, since the installed version is 3.6 and upstream fixed the issue in 3.8.

And the limit matters as much as the method: the changelog proves the positive only. “Not named” means “this changelog does not say so”. It never means “you are vulnerable”; the fix may predate the packaging, the CVE may not apply to this branch, or the entry may be worded differently. A tool that reports “NOT FIXED” from this check is making a claim the evidence cannot support.

Two things to say to a scanner, then. Its version match is wrong by design — stage 1 — and the changelog is the counter-evidence. And “zero pending updates” is not a state your machine can reach — stage 2 — so an audit item written that way can only ever be failed.

A worked diagnosis: the patch count that would not go to zero

A small production host. Ubuntu 24.04, one Go application in /opt/app/bin/server behind nginx, started by a supervisor that is not systemd. unattended-upgrades is installed and enabled, as it is on every Ubuntu cloud image, and nobody has configured it.

Every obvious observable says nothing is wrong.

$ sudo tail -2 /var/log/unattended-upgrades/unattended-upgrades.log
2026-09-01 06:41:12 INFO Packages that will be upgraded: libssl3t64 openssl
2026-09-01 06:41:31 INFO All upgrades installed

$ ls /var/run/reboot-required
ls: cannot access '/var/run/reboot-required': No such file or directory

$ sudo needrestart -b
NEEDRESTART-VER: 3.6
NEEDRESTART-KCUR: 6.8.0-138-generic
NEEDRESTART-KSTA: 1

$ systemctl --failed
0 loaded units listed.

Nightly success in the log, no reboot flag, a clean restart check, no failed units, and openssl version prints what it printed last year. The only thing that looks odd is a monitoring check that has been alerting for four months: apt list --upgradable never reaches zero. It sits at 22 and does not move.

Someone reasons that unattended-upgrades must be broken, adds -updates to the allowed origins, and the count goes to zero the next morning. The alert clears. Three weeks later the box is compromised through a library that was patched two months earlier.

The hinge routes it in one command.

$ sudo needrestart -v -b 2>&1 | grep -E 'uses (deleted|obsolete)|^NEEDRESTART-'
NEEDRESTART-VER: 3.6
[main] #1183 uses deleted /usr/lib/x86_64-linux-gnu/libssl.so.3
[main] #1184 uses deleted /usr/lib/x86_64-linux-gnu/libssl.so.3
NEEDRESTART-KCUR: 6.8.0-138-generic
NEEDRESTART-KSTA: 1

Two uses deleted lines. No SVC line, no PID line. That is the hinge’s second branch — it found them and dropped them — and two commands say why:

$ sudo dpkg -S "$(readlink -f /proc/1183/exe)"
dpkg-query: no path found matching pattern /opt/app/bin/server

$ cat /proc/1183/cgroup
0::/system.slice/supervisor.service

The binary is not dpkg-owned, so the package hook returns nothing; the cgroup names the supervisor rather than this process, so there is no unit to restart. On the Red Hat side the same process is dropped by if pkg is None: continue.

The fix is not the obvious one. Restarting the supervisor is correct and you should do it, but it is the symptom. The actual defect is that the count was right and the log was wrong. Those 22 pending updates were stage 2’s permanent skew, a benign number that cannot be cleared, while All upgrades installed had been printed nightly for four months from if len(to_upgrade) == 0:, a statement about an empty set and not about the machine. The change that made the machine less safe was made to silence an alert on the one number telling the truth, while the number that was lying went unquestioned because it was phrased as a sentence rather than a count.

And note where the hinge points: past the cause. It does not say “your binary is unpackaged”. It says the tool found something and did not mention it. The unpackaged Go binary is one instance; the same gap explains the container that shows nothing because there is nothing to find, the Python process whose imported module was patched, and the kernel check that answers 0 for “I could not tell”. One silence, four causes.

The moral, and it is the page in one line: a tool that reports by name is silent about everything it cannot name — and on a healthy-looking machine that silence is indistinguishable from good news.

Symptoms, and which stage owns them

What you seeStageWhat to run, or what it is
apt list --upgradable never reaches zero on a machine that patches nightly2correct and permanent; do not “fix” it by adding -updates
A scanner calls a fully patched machine vulnerable1, 7it is reading the upstream version; answer with the changelog
openssl version identical on two machines patched a year apart1openssl version -a | grep 'built on'
Automatic updates enabled, and nothing is ever installed3dnf-automatic ships apply_updates = no
“No services need to be restarted” on a machine that just took a security upgrade4the hinge command; count the two lists
A service still running old code after a successful restart5a supervisor, a container image, or a data file read at startup
A restart tool that reports nothing for a patched Python module4it checks the entry script’s mtime and nothing else
An upgrade bounced a service you were using4Ubuntu’s APT hook restarts automatically by default
dbus, docker or NetworkManager never restarted by anything4they are on the shipped deny-list, and it does not say so
/var/run/reboot-required absent on a machine that needs a reboot6no update-notifier-common; compare uname -r with /boot
dnf needs-restarting -r exits 0 after an OpenSSL update6true, and about ten packages that do not include openssl
NEEDRESTART-KSTA: 06“I could not find your kernel”, not “no reboot needed”
Code running that never appears in apt list --upgradable5the unowned-mappings command; expect a long list

Before you call it done

  1. Find out what is actually scheduled, and what it is allowed to install. systemctl list-timers --all | grep -Ei 'apt-daily|unattended|dnf-automatic' and apt-config dump | grep -iE 'APT::Periodic|Unattended-Upgrade::'. A timer without a period, or a period without a timer, patches nothing.
  2. On Fedora or RHEL, read /etc/dnf/automatic.conf before enabling anything. If apply_updates still says no, nothing has ever been installed.
  3. Run the hinge command and count the two lists. Every uses deleted line without a matching SVC or PID line is something nothing on this machine will ever restart.
  4. List the mapped files no package owns, and decide who is responsible for each. That list is the part of the machine your package manager cannot patch.
  5. Stop treating the pending-updates count as an alert condition. Alert on growth in it, or on the age of the oldest held package — not on it being non-zero, which it cannot be.
  6. Check the reboot question directly — uname -r against the newest /boot/vmlinuz-* — rather than through a flag file that may never be written.
  7. Prove one CVE, once, by hand, using the changelog method in stage 7. Do it before you need it, so that the day somebody asks, you already know your own machine’s answer.

Related reading

  • The Life of a Package — the eight stages that get the correct file onto the disk in the first place, which is the half this page assumes.
  • Hardening a Public-Facing Service — the other half of the same afternoon, and the page whose one-clause-short sentence this one exists to finish.
  • Securing a New Server — where automatic updates get switched on, and what else belongs in the first hour.