The patching gap: why monthly cycles aren't enough for self-hosted services
apt upgrade doesn't touch Plex. Or Jellyfin. Or
Grafana, Pi-hole, Sonarr, Radarr, Portainer, the rest of the
services that make a homelab actually useful. Most homelab
operators have a monthly distro patch cycle and assume that
covers everything — it covers maybe 40% of the running
software. This is the patching gap, and ignoring it is how
modern homelabs get compromised.
What apt upgrade actually patches
Run apt list --installed on a typical homelab
box. It returns 1,500–3,000 packages: the Linux kernel,
libc, openssl, openssh, systemd, every shared library, and
whatever distro-packaged daemons you installed
(nginx, postgresql, etc.). When a
CVE is disclosed against any of those, the distro
maintainers backport a fix and push it through the security
repos. apt upgrade picks it up. This pipeline
is genuinely excellent and, for the software it covers, is
the gold standard.
But that 1,500–3,000 number doesn't include:
Software outside distro repos
Plex / Jellyfin / Emby
Installed via vendor-provided .deb /
.rpm from plex.tv/downloads,
jellyfin.org/downloads, etc. Each ships its
own auto-update mechanism (or doesn't), and those updates
are independent of apt upgrade. Jellyfin had
multiple critical authentication bypasses across 2023–2024;
operators on stale versions remained vulnerable for months.
Grafana / Prometheus / monitoring stack
Most homelab operators install Grafana via
grafana.com/oss's repo, which IS part of apt
after configuration — but it's a third-party repo, and the
cadence is set by Grafana Labs (not your distro's security
team). Grafana 8.5 had CVE-2022-31097 (XSS in the alerting
module) that wasn't patched in some downstream packages
until weeks after upstream.
The *arr suite
Sonarr, Radarr, Lidarr, Readarr, Prowlarr — all installed via Mono / .NET runtimes from custom repos. Each has its own update mechanism. The *arr suite is a poster child for the patching gap: huge install base, security history including credential leakage and SSRF bugs, and almost nobody applies updates promptly.
Docker images
Anything you run as docker pull foo:latest +
docker run. The container's contents are
independent of the host's apt. The image you
pulled six months ago might run an openssl from 2022 with
five known CVEs unpatched. apt upgrade on the
host doesn't help; you need docker pull foo:latest
again, then docker stop && docker run.
Most homelab Docker stacks don't refresh images proactively.
Pi-hole, AdGuard, Mikrotik, UniFi
Each has its own update channel. Pi-hole's
pihole -up covers Pi-hole + its bundled tooling.
Mikrotik's RouterOS updates via the device UI. UniFi
Controllers ship .deb but with their own apt repo. None of
these are in the distro security feed.
Anything you compiled from source
That git clone && make install you did
to get the latest version of a tool that wasn't in apt's
repos? It's now stale forever unless you remember it exists.
Operators rarely do.
End-of-life packages your distro hasn't dropped yet
A subtler version of the gap: the distro DOES ship a package and DOES still update it, but the upstream version is past end-of-life and no longer gets fixes. Examples:
- Debian 11 carried Node.js 12 long after Node.js 12 upstream EOL. Distro's security advisories went silent; new Node CVEs were never backported. Operators saw "no updates available" — technically true, dangerously misleading.
- Ubuntu 20.04 carries python3.8 well past upstream's 2024 sunset. Apps depending on system Python on these hosts are running unmaintained code.
- RHEL/Rocky 8's default php is 7.2, EOL since 2020. Software Collections gives newer versions, but operators often run the default.
The disclosure-to-patch lag for the gap
For distro-packaged software, the gap between upstream disclosure and distro-shipped patch is usually 0 to 24 hours. For non-distro software, the gap is whatever the operator's reaction time is — typically days to weeks to months.
The asymmetry is what attackers exploit. Disclosure of a CVE in (say) Plex means:
- Day 0: CVE published, exploit code on GitHub within hours.
- Day 1: Shodan crawls update; every public Plex instance is in a queryable list.
- Day 2–3: Mass exploitation begins. Operators on Plex's auto-update channel are already patched. Operators without auto-update are not.
- Day 7+: 60–80% of public instances still vulnerable. That's the long tail of attack opportunity.
The operators who escape are either (a) on auto-update for every service, or (b) running a CVE scanner that surfaces "Plex on this host is version X, latest is Y" the morning after disclosure.
Why a CVE scanner catches the gap when apt upgrade can't
A CVE scanner doesn't care which package manager installed
something. It reads the binary's reported version (via
plex --version, the binary's metadata, or HTTP
headers from the running daemon) and matches against the
CVE feed. For Plex, the scanner sees "Plex Media Server
1.29.0.6244-819124617" running on port 32400, looks up the
CVE feed for plexinc:plex_media_server, and reports any
matches.
apt upgrade sees nothing because Plex isn't an
apt package on most installs. The CVE scanner sees what's
actually running.
Closing the gap without becoming a full-time updater
Five practices, ordered by ROI:
1. Auto-update everything that supports it
Most modern self-hosted software has an auto-update
mechanism. Plex updates itself by default. Jellyfin via
its package's hooks. Pi-hole via pihole -up
cron. Docker images via Watchtower (with caveats — it
WILL break things that need a config migration). Each
auto-update you enable is one fewer manual touch per CVE.
Caveat: auto-update without testing means a broken release breaks your service silently. For things you don't watch regularly (the *arr suite, downloaders), this is fine. For things you depend on (DNS, SSO, reverse proxy), consider a 24h delay window so you can intervene if a release ships broken.
2. Subscribe to the security mailing lists
For software you can't auto-update but is critical (UniFi Controller, Mikrotik, pfSense, anything in your network path), subscribe to the project's security announce list. That's free and gets you notified day-zero. Practical, low maintenance.
3. Run a CVE scanner against the running fleet
For everything else — the long tail of services that don't auto-update and aren't in any mailing list you watch — a scanner that reads installed-version-from-binary and compares against the feed is the only sustainable answer. Daily scans catch things within ~24h of disclosure even without your active attention.
4. Keep a "what's actually running" inventory
Half the patching gap problem is forgetting what you've installed. A periodic scan (or a manual quarterly review) that lists every running service across every host catches the test installations, the abandoned-but-still-running Docker containers, the helper scripts a former housemate set up. You can't patch what you've forgotten about.
5. Decommission ruthlessly
The single biggest patching gap reducer is having less software running. Every service installed "to play with it" that you forgot about is a chunk of code receiving no attention while accruing CVEs. Quarterly: list everything, kill anything not actively used. The lab gets smaller and the patch surface shrinks proportionally.
What Noxen does for the patching gap
Noxen scans installed binaries, not just
apt-managed packages. The CVE matcher reads version strings
from package managers AND from running services (HTTP
banner, executable --version output where
available). When a finding fires for Plex 1.29 with CVE
coverage, you see it in the morning report regardless of
whether Plex came from apt, the vendor's deb, a Docker
image, or a manual install.
The exposed-admin-surface probe complements this — it detects the panels you forgot you'd exposed, not just the versions of the panels you remember. Running both nightly gets you closest to "I know what's actually running on my fleet" without becoming a full-time updater.
$79 one-time at launch.