What this check reads
The check simulates an upgrade with the first package manager it finds and filters the result for database packages matching mysql|mariadb|postgres|mongo|redis. On apt that is apt-get -s -o Debug::NoLocking=true upgrade, counting the ^Inst lines; any of those lines that also contains security (the archive name, for example jammy-security) is counted a second time as a security update. On dnf and yum it counts matching lines from check-update, on zypper the ^v lines of list-updates, and on apk the output of apk version -l '<' (whose filter omits mongo); none of those four can tell a security update from an ordinary one. Zero matching lines is the pass threshold: one or more security lines on apt is the FAIL, one or more lines without that marker is the WARN, and no supported package manager at all is the SKIP.
When it applies
Runs on any host with a supported package manager (apt-get, dnf, yum, zypper, apk); anything else SKIPs. No root is needed - the apt path uses a simulated upgrade (-s) with locking disabled. It does not require a database to be running, only that database packages are installed and visible to the package manager. Two important scope limits: it reads the cached package lists (a stale apt update gives a stale answer), and only apt distinguishes security updates - on dnf/yum/zypper/apk every pending update is a WARN, never a FAIL. Engines installed from a tarball, from source, or in a container are invisible.
What each result means
| Result | When | What it means |
|---|---|---|
| PASS | No pending DB package updates | The package manager's cached lists show nothing pending for the database packages it can see. Stale lists give a stale pass, so refresh them first. |
| FAIL | apt only: a pending update comes from a `-security` pocket | A published security fix for a database package is sitting unapplied on this host. Apply it now rather than at the next maintenance window. |
| WARN | Pending updates without the security marker (or any pending on non-apt) | Database package updates are pending but nothing marked them as security fixes. On dnf, yum, zypper and apk that marker is never available, so this can still be a security update. |
| SKIP | No supported package manager | Nothing was verified: none of apt-get, dnf, yum, zypper or apk was on PATH, so no version comparison happened at all. |
Why it matters
Database engines are high-value targets and CVEs (auth bypass, privilege escalation, RCE) are weaponised quickly. OWASP: install security patches promptly. NIST SP 800-40r4 patch management. CIS benchmarks for each engine start with "Ensure the latest version is installed".
Why it fails, and when it is wrong
- apt uses the cached package lists. If
apt updatehas not run recently the view is stale in both directions.unattended-upgradesand theapt-daily.timernormally refresh daily. - Packages installed from vendor repos (MySQL APT repo, PGDG, MongoDB repo) are included as long as the repo is configured. Tarball or Docker installs are invisible.
- Pinned/held packages (
apt-mark hold) do not appear in the simulation. - On dnf,
check-updateexits 100 when updates exist; the script uses2>/dev/nulland counts lines, which works. Usednf updateinfo list securityto see only security fixes.
How to fix it
Refresh the package lists first
The check reads the cached package lists and never refreshes them, so counting pending updates against stale metadata reports zero on a server that is weeks behind.
sudo apt update # Debian/Ubuntu
sudo dnf makecache # RHEL familyList what is pending for the engines
Filter the pending updates down to the engines before you upgrade anything, so you know what the window has to cover:
apt-get -s upgrade | grep '^Inst ' | grep -iE 'mysql|mariadb|postgres|mongo|redis' # Debian/Ubuntu
dnf check-update | grep -iE 'mysql|mariadb|postgres|mongo|redis' # RHEL familyApply the security updates
Upgrade the named database packages in a window where a restart is acceptable:
sudo apt upgrade mysql-server postgresql-16 redis-server # Debian/Ubuntu
sudo dnf update mysql-server postgresql-server mongodb-org redis # RHEL family
sudo dnf upgrade --security # RHEL family, security fixes onlyPlan minor-version upgrades during a window; PostgreSQL minor upgrades need a restart, major ones need pg_upgrade.
Confirm the engine restarted on the new version
Package upgrades do not always restart the daemon, so the installed package can be current while the process still serving queries is the old binary. Check the running version afterwards, not just the installed one, and restart the service if it is behind.
Verify the fix
# Debian/Ubuntu - refresh first, then confirm nothing database-related is pending:
sudo apt-get update
apt-get -s -o Debug::NoLocking=true upgrade | grep '^Inst ' | grep -iE 'mysql|mariadb|postgres|mongo|redis'
# expected: no output
# RHEL family - and specifically the security-only view the check cannot give you:
dnf -q check-update 'mysql*' 'mariadb*' 'postgresql*' 'mongodb*' 'redis*'
dnf updateinfo list security
# Confirm the running server is actually on the new version (a package upgrade needs a restart):
mysql --version; sudo mysql -N -e 'SELECT VERSION()'
psql --version; sudo -u postgres psql -X -tAc 'SHOW server_version'Debugging
None of the five managers is on PATH. If the engine was installed from a tarball or source, no package-manager check can see it; track its version manually against the vendor's advisories.
The package lists are probably stale. Run sudo apt-get update (or dnf makecache) and re-run the audit. Also check for held packages, which never appear in the simulation: apt-mark showhold, dnf versionlock list.
Either the upgrade did not include those packages, or the running server is still the old binary. Compare package version with the live server version using the commands above, and restart the service.
Expected. Only the apt branch inspects the archive pocket for the word security. Use dnf updateinfo list security to classify it.
apt-get -s -o Debug::NoLocking=true upgrade | grep '^Inst ' | grep -iE 'mysql|mariadb|postgres|mongo|redis'; lines whose archive contains security are what turn the WARN into a FAIL.
Sources
- apt-get(8) simulation flags
- dnf updateinfo (security-only listing)
- NIST SP 800-40 Rev. 4 Guide to Enterprise Patch Management Planning
- MySQL security advisories (Oracle CPU)
- PostgreSQL: security information
- PostgreSQL: versioning and release schedule
- MongoDB security alerts
- Redis security advisories
- Ubuntu security notices
How the script reads this
Next
Re-run the Configuration & Hardening audit after applying the fix and confirm this check moves to PASS. CtrlOps runs all 7 checks over your existing SSH connection and scores the result, so the change is visible without reading another config file.
All 7 Configuration & Hardening fixes