What this check reads
For MySQL the check first reads SELECT @@log_bin. If binary logging is on it also reads @@datadir and @@log_bin_basename, takes the dirname of the latter, resolves both paths to a mount point with df -P (field 6 of the second line), and records mysql:binlog-same-disk when the two mount points are identical or mysql:binlog-separate when they differ; with binary logging off it records mysql:binlog-off and moves on. For PostgreSQL it reads SHOW data_directory and applies a different test entirely, test -L "$PGDATA/pg_wal": a symlink counts as postgres:wal-separate and anything else, including a real mount point, as postgres:wal-same-disk. One same-disk verdict from either engine is the threshold that turns the whole check into a WARN.
When it applies
Runs only for engines it can query: the MySQL branch needs MYOK=1, the PostgreSQL branch needs PGOK=1; with neither, SKIP. Only MySQL/MariaDB and PostgreSQL have branches - MongoDB and Redis are not considered. The MySQL branch only evaluates placement when binary logging is on; with log_bin off it records binlog-off and contributes to a PASS. The two branches use different tests: MySQL compares df -P mount points (correct), PostgreSQL only asks whether $PGDATA/pg_wal is a symlink - so a pg_wal that is its own mount point is wrongly reported as sharing the disk. This is a known script issue, listed in the README.
What each result means
| Result | When | What it means |
|---|---|---|
| SKIP | Neither engine accessible | Nothing was verified: neither the MySQL nor the PostgreSQL probe could reach a server, so no log placement was examined. |
| WARN | Any engine's log shares the data disk | At least one engine writes its log to the same mount point as its data files. On PostgreSQL confirm with df first, because the check only tests for a symlink. |
| PASS | Logs separate, or binlog off (noted for review) | No engine reported a shared disk. A mysql:binlog-off entry inside this pass still deserves attention: it means MySQL has no point-in-time recovery. |
Why it matters
OWASP Database Security Cheat Sheet lists "transaction logs on a separate disk" under hardening. Availability and recovery: if one partition fills, both writes and point-in-time recovery stop together. Separate devices also isolate I/O and let you keep WAL/binlogs on a device with different retention.
Why it fails, and when it is wrong
- PostgreSQL
pg_walthat is a mount point rather than a symlink is reported as same-disk: false WARN. The check should comparedflike the MySQL branch; this is a known script issue. - Single-disk VPS plans make this unavoidable; treat as an accepted risk and rely on disk-space monitoring and offsite backups.
binlog-offis reported inside a PASS but deserves attention: without binary logs you cannot do point-in-time recovery for MySQL.- LVM/btrfs subvolumes on the same physical device count as separate mount points; the check measures partitions, not devices.
How to fix it
- PostgreSQL: stop the server, move
pg_walto the new volume,ln -s /mnt/wal/pg_wal $PGDATA/pg_wal, start. (initdb --waldirfor new clusters.) - MySQL: set
log_bin=/mnt/binlog/mysql-binandlog_bin_index, move existing files, restart. - If only one disk exists, set
binlog_expire_logs_seconds/wal_keep_sizeand alert on free space.
Verify the fix
# PostgreSQL - compare real mount points, which is what actually matters:
PGDATA=$(sudo -u postgres psql -X -tAc 'SHOW data_directory')
df -P "$PGDATA" "$PGDATA/pg_wal" | awk 'NR>1{print $6, $1}'
# expected: two different mount points
# MySQL - the same comparison the check makes:
sudo mysql -N -e 'SELECT @@log_bin, @@datadir, @@log_bin_basename'
df -P "$(sudo mysql -N -e 'SELECT @@datadir')" \
"$(dirname "$(sudo mysql -N -e 'SELECT @@log_bin_basename')")" | awk 'NR>1{print $6}'
# expected: two different mount pointsDebugging
MYOK and PGOK are both 0. Test with sudo mysql -N -e 'SELECT 1' and sudo -u postgres psql -X -tAc 'SELECT 1'.
The known false positive: the check tests test -L "$PGDATA/pg_wal" and a mount point is not a symlink. Confirm with the df -P command above; if the mount points differ, record it as a false positive. There is no way to make the check pass short of using a symlink instead of a mount.
Worth a second look. Binary logging being off is not a placement problem, but it means no point-in-time recovery for MySQL. Turn it on if your recovery objective needs it.
Accurate and usually unavoidable. Compensate with free-space alerting and offsite backups, and record it as accepted.
The check measures mount points, not physical devices, so two subvolumes on one disk look separate. Verify the underlying device with lsblk/findmnt if isolation is the goal.
Sources
- PostgreSQL: WAL Internals (relocating pg_wal)
- PostgreSQL: initdb --waldir
- MySQL 8.4: The Binary Log,
log_bin_basename - MySQL 8.4: binlog_expire_logs_seconds
- OWASP Database Security Cheat Sheet (Hardening)
- df(1)
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