Patching and Secrets
Topic 68

Patching and Secrets

Hardening

Patching is keeping installed packages current with the security fixes the distribution publishes; secret management is keeping the credentials those services need — database passwords, API tokens, TLS private keys — out of source control, out of process listings, and off world-readable disk. They are separate disciplines that fail the same way: a known CVE that sat unpatched for weeks, or a password committed to a Git repository, is the most common root cause in real server compromises, and both are entirely preventable with a routine.

The operational consequence is that a server is never "done." On Debian and Ubuntu, security updates ship continuously through a dedicated -security archive, and the gap between a public CVE and an attacker scanning for it is now measured in hours, not days. A fleet you patch by hand once a quarter is a fleet that runs known-exploitable code most of the year — and the secrets on those boxes are exactly what the exploit is after.

The APT Update Model

On Debian and Ubuntu, patching is two distinct operations. apt update refreshes the package index from the configured sources — it changes nothing on disk except the cache. apt upgrade then installs newer versions of already-installed packages, and apt full-upgrade additionally removes packages where that is required to complete an upgrade (the old dist-upgrade). Running upgrade without a preceding update just reinstalls what you already have, which is the most common reason "I patched it" turns out to be false.

Security fixes come from a separate archive — $RELEASE-security on Debian, $RELEASE-security on Ubuntu — kept distinct from the general updates pocket so you can apply only security content if you want a smaller, lower-risk change window. The contract is that within a stable release the distribution backports the fix to the existing version rather than bumping you to a new upstream major version, so openssh-server stays at the same feature set and only the vulnerability is closed. That stability is the whole point of running stable: you patch without your config or behavior shifting underneath you.

# refresh the index, then see exactly what security has waiting
sudo apt update
apt list --upgradable 2>/dev/null | grep -security

# apply only security updates, non-interactively
sudo unattended-upgrade --dry-run -d

# a kernel was updated if this file exists
ls /var/run/reboot-required && echo "reboot needed"

Unattended Upgrades

The unattended-upgrades package applies security updates automatically on a timer, and on a server it should be on. The default configuration in /etc/apt/apt.conf.d/50unattended-upgrades restricts it to the -security origin only, so it closes vulnerabilities without dragging in feature changes that might surprise you. Pair it with /etc/apt/apt.conf.d/20auto-upgrades, which controls how often the index is refreshed and the upgrade run fires via the apt-daily and apt-daily-upgrade systemd timers.

Two settings decide whether automation is safe to leave running. Unattended-Upgrade::Automatic-Reboot defaults to false — a kernel or glibc update lands but the new code is not active until you reboot, which on a server you usually want to schedule rather than have happen mid-traffic. Unattended-Upgrade::Mail sends a report so a failed or held-back upgrade is visible instead of silent. The failure mode to avoid is the opposite of negligence: an unattended reboot at an unplanned hour because Automatic-Reboot was set to true with no Automatic-Reboot-Time.

SettingDefaultServer recommendation
Allowed-Origins-security onlyKeep security-only unless you accept upgrade-pocket risk
Automatic-Rebootfalsetrue only with a fixed Automatic-Reboot-Time
MailunsetSet to a monitored address
Remove-Unused-Kernel-PackagestrueKeep true so /boot does not fill

Live Kernel Patching and Reboots

A user-space fix — openssl, bash, nginx — takes effect when you restart the affected service; only a kernel update genuinely needs a reboot to activate. The tool that tells you which running processes still link an outdated library is needrestart, installed by default on recent Ubuntu, which after every apt run lists the services holding stale code and can restart them. Relying on a clean reboot to "make sure everything is patched" wastes downtime on fixes that a service restart would have applied immediately.

For the kernel itself, live patching closes the reboot gap for critical CVEs. Ubuntu's Livepatch and the equivalent kpatch/kgraft mechanisms inject fixed code into the running kernel without a reboot, buying time until a maintenance window. They are not a replacement for rebooting onto the new kernel — a livepatch covers a curated set of high-severity fixes, not every change, and the running kernel is still the old binary on disk. Treat livepatch as the thing that lets you reboot on your schedule instead of the attacker's.

Secrets at Rest and in the Environment

The baseline for a secret on disk is a file owned by the service account with mode 0600 (or 0640 with a dedicated group), never world-readable and never in a directory anyone else can traverse. A TLS private key at 0644 is readable by every account on the box, which defeats the certificate; check with find /etc -name '*.key' -perm /044 to catch the ones that slipped. Secrets do not belong in shell history, in command arguments (anyone can read them in /proc and ps), or committed to a repository where git log preserves them forever even after a later deletion.

Passing secrets through environment variables is convenient and leakier than it looks: a child process inherits the whole environment, /proc/PID/environ exposes it to anyone who can read the process, and crash handlers and error trackers routinely serialize the environment into logs. systemd's LoadCredential= and SetCredential= are the modern answer — the unit receives the secret as a file under a ramfs path in $CREDENTIALS_DIRECTORY — ramfs, not tmpfs, so it is never swapped to disk — visible only to that service, never to its children or to other units. For encryption keys that must be wrapped at rest, systemd-creds encrypt ties the ciphertext to the host TPM so a stolen disk image yields nothing usable.

# lock down an existing key file and its directory
sudo chown app:app /etc/app/tls.key
sudo chmod 0600 /etc/app/tls.key
sudo chmod 0750 /etc/app

# hand a secret to a service via systemd, not the environment
# in the unit's [Service] section:
LoadCredential=db-password:/etc/app/db.pass
# the app reads $CREDENTIALS_DIRECTORY/db-password at runtime

For anything beyond a single host, a dedicated secret store — HashiCorp Vault, or a cloud KMS-backed store — adds rotation, audit logging, and short-lived dynamic credentials, so a leaked token expires on its own instead of living until someone notices. The principle is the same at every scale: the secret is encrypted at rest, access is logged, and the application fetches it at start time rather than carrying a static copy in its image or its repo.

Unattended Upgrades vs Manual Patching

Unattended upgrades — the host stays current with the -security archive on its own, so the exposure window after a CVE is hours, not weeks. The cost is less control over timing: a service restart or a scheduled reboot happens without a human in the loop. Choose it for the bulk of a fleet, where currency matters more than orchestrating each change.

Manual patching — you apply updates inside a defined change window, test first, and reboot on a plan. The trade is that the box runs known-vulnerable code until the next window, which on a quarterly cadence means most of the year. Reserve it for the handful of hosts where an unplanned restart is unacceptable — a stateful database primary, a single-node appliance — and pair it with livepatch to shrink the exposure.

Common Mistakes
  • Running apt upgrade without apt update first — you reinstall the versions already on disk and walk away believing the box is patched while the index is stale.
  • Leaving unattended-upgrades off on servers because of one bad memory of an automatic update, then patching by hand quarterly and running known-exploitable code for months between passes.
  • Setting Automatic-Reboot "true" with no Automatic-Reboot-Time — a kernel update triggers a reboot at a random hour and takes production down without warning.
  • Treating a reboot as the way to apply every patch, when a user-space fix only needs the service restarted; needrestart tells you which processes still hold stale libraries.
  • Passing a password as a command-line argument or environment variable — it shows up in ps, in /proc/PID/environ, and in any crash report that dumps the environment.
  • Committing a secret to Git "temporarily" — git log and every clone keep it permanently, so the fix is rotating the credential, not deleting the line in a later commit.
  • TLS private keys or .pem files left at mode 0644 — readable by every local account, which silently negates the certificate's protection.
Best Practices
  • Enable unattended-upgrades on every server, scoped to the -security origin, with Mail set to a monitored address so a held-back upgrade is visible.
  • Run apt update immediately before any apt upgrade, and check apt list --upgradable | grep -security to see exactly what the change closes.
  • Schedule reboots with a fixed Automatic-Reboot-Time and watch for /var/run/reboot-required rather than rebooting blindly or never.
  • Restart affected services with needrestart after patching user-space libraries instead of waiting for a reboot to activate the fix.
  • Use Livepatch or kpatch for critical kernel CVEs to reboot on your maintenance schedule, but still reboot onto the new kernel within the window.
  • Hand secrets to services with systemd LoadCredential= so they land in a per-service ramfs path, never in the inherited environment or a child process.
  • Set key files to 0600 owned by the service account, keep them out of Git, and rotate any credential the moment it is exposed rather than just deleting it.
Comparable toolsRed Hat / dnfdnf update --security and dnf-automatic for unattended patching; kpatch for live kernel fixesHashiCorp Vault — central secret store with dynamic, short-lived credentials, rotation, and audit logging across hostsWindows — Windows Update / WSUS for patching and DPAPI / Credential Manager for at-rest secrets

Knowledge Check

Why does running apt upgrade without apt update first often leave a server unpatched?

  • apt update refreshes the package index; without it, upgrade only sees the versions already known and reinstalls what is on disk
  • apt upgrade requires root privilege and silently no-ops without any error message when it is run by an ordinary unprivileged user
  • apt upgrade only ever applies feature updates and never security fixes, unless update first enables the dedicated security archive
  • apt update is the command that actually installs the new packages on disk, while upgrade merely downloads the archives to the local cache

A fix lands for an openssl CVE. Why is a full reboot usually unnecessary?

  • openssl is a user-space library; restarting the services that link it activates the fix, and needrestart identifies which ones still hold stale code
  • The dynamic linker hot-swaps the newly installed shared object into every already-running process automatically and in place, so nothing at all needs restarting
  • The kernel transparently reloads all of a process's mapped libraries from disk on its next system call, so each one picks up the new code
  • Only loadable kernel modules ever require a reboot to take effect; every user-space library fix simply applies live with no further action

What is the trade-off of setting Unattended-Upgrade::Automatic-Reboot "true" with no Automatic-Reboot-Time?

  • Kernel updates activate promptly, but the host can reboot at an unplanned hour and take production down without warning
  • Security updates stop applying to the host entirely and queue up indefinitely until an explicit automatic reboot time is also configured
  • The machine reboots only once it detects that no interactive users are currently logged in over any session, so production is never affected
  • It quietly disables the -security origin restriction and starts pulling in general feature upgrades too

Why is systemd LoadCredential= safer than passing a secret through an environment variable?

  • The secret is delivered as a file in a per-service ramfs path, not inherited by child processes or readable via /proc/PID/environ
  • It transparently encrypts the variable while it sits in process memory, so even the service that owns it can never read back the plaintext value
  • It stores the secret as a structured record inside the systemd journal, which is access-controlled and readable only by root by default
  • It rotates the credential to a freshly generated value automatically on every single service restart of the unit

You got correct