What gets checked

Every finding carries a stable rule id (used in suppressions and the JSON output), a severity, the affected service, and the evidence that triggered it.

How to read severities:

  • RED — restore-threatening. If you needed this backup today, it would fail you: nothing is backed up, the backup is stale or empty, or it is unrestorable.

  • YELLOW — hygiene or early warning. Not an emergency, but it will become one if ignored.

Mount drift (dockerHosts)

The classic silent killer: your backup job archives the path the compose file declares, but the container actually writes somewhere else.

Rule Severity Meaning

mount-drift/missing-live

RED

a declared bind-mount/volume is not mounted in the running container

mount-drift/source-mismatch

RED

mounted, but from a different source path than declared — backups read the wrong place

mount-drift/extra-live

YELLOW

the container mounts something the compose file doesn’t declare

mount-drift/unresolved-declared

YELLOW

the compose project’s config can’t be resolved on the host (e.g. stacks deployed by an external tool), so drift is uncheckable — one aggregated finding per project

Stale configs (dockerHosts)

config-drift/stale-config

YELLOW

the running container was created from an older compose config than what is on disk — the next restart silently changes behavior

Database dumps (logicalDbBackups)

db-backup/uncovered

RED

a live database container has no dump at all

db-backup/stale

RED

newest dump is older than maxDumpAgeHours

db-backup/empty

RED

the dump file is zero bytes

db-backup/small

YELLOW

the dump is suspiciously small for a real database

db-backup/method-mismatch

RED

the job’s dump method doesn’t fit the container’s engine (e.g. pg_dumpall against a DocumentDB image) — the file exists but cannot restore that database

db-backup/unmatched

RED

with requireProdNaming: a running DB container matches neither the prod convention nor the skip list — it will never be backed up until someone decides

Image backups — Proxmox (pveNodes)

image-backup/uncovered

RED

a VM/LXC has no image backup in any configured storage

image-backup/stale

RED

newest image backup is older than pbsMaxSnapshotAgeHours

image-backup/empty

RED

the backup artifact is zero-sized

image-backup/orphan

YELLOW

a backup exists for a guest that no longer does — usually a rename/migration leftover worth pruning (or a sign the new guest isn’t covered)

three-two-one/image-local-only

YELLOW

every image backup of a guest lives on non-shared storage of its own node — one disk or host failure takes the guest and all its copies together. Give it a copy that survives the node (PBS/shared storage, or sync the dump dir off the box)

Off-site sync jobs (offsiteJobs)

Any scheduled rclone (or rclone-style) sync script that logs its runs with === <yyyy-MM-dd HH:mm> …​ sync start === and === sync finished rc=N === markers. With rcloneRemote set, the remote’s occupancy also feeds the storage capacity check (rclone about — not every backend supports it).

offsite/never-ran

RED

the log has no runs at all — the job never ran, or its script doesn’t write the documented markers

offsite/failed

RED

the last run exited non-zero (or never logged a finish line — a killed run)

offsite/stale

RED

no successful run within maxSyncAgeHours

pbsOffsite (below) is the older, single-job PBS-specific flavor of this check and keeps working; new configs should prefer offsiteJobs.

PBS hygiene (pbsMaintenance, pbsOffsite)

pbs/verify-failed

RED

the last completed verification reported errors — chunks in the datastore are corrupt

pbs/verify-never-ran

YELLOW

verify jobs exist but none has ever completed

pbs/verify-stale

YELLOW

last successful verification is older than maxVerifyAgeHours

pbs/no-verify-jobs

YELLOW

the datastore has no verify job at all — backups are never integrity-checked

pbs/gc-never-ran

YELLOW

garbage collection has never run on the datastore

pbs/gc-stale

YELLOW

GC hasn’t run within maxGcAgeDays

pbs/offsite-failed

RED

the last off-site sync exited non-zero

pbs/offsite-stale

RED

no successful off-site sync within maxSyncAgeHours

pbs/sync-job-never-ran

YELLOW

PBS→PBS sync job(s) exist (auto-discovered) but no sync has ever completed

pbs/sync-job-failed

RED

the last completed PBS sync did not succeed — the second PBS is behind

pbs/sync-job-stale

RED

no successful PBS sync within maxSyncJobAgeHours

pbs/host-backup-missing

RED

a watched proxmox-backup-client host (hostBackups) has no host backups at all in the datastore

pbs/host-backup-stale

RED

the newest host backup of a watched host is older than maxHostBackupAgeHours

File-level backups (fileBackups)

Apply per configured source, across all kinds (restic, borg, kopia, snapper, dir, haos):

file-backup/uncovered

RED

the source produced no artifacts at all — empty repo, empty directory, no full backup

file-backup/stale

RED

newest artifact is older than the source’s maxAgeHours

file-backup/empty

RED

the newest artifact is zero-sized

Restore canary (fileBackups with canaryPath — restic/borg)

Snapshots existing is not the same as backups restoring. Set canaryPath on a restic or borg source to a small file that must always be in the backup (a config file is ideal): every audit then streams that file out of the latest snapshot and counts the bytes on the host — a real end-to-end restore through decryption and chunk reads, with nothing written anywhere and no content leaving the machine.

restore-canary/failed

RED

the canary restored as 0 bytes — wrong passphrase, corrupt repo, or the path fell out of the backup. Either way the backup just failed its restore drill

for borg, give the path as stored in the archive — the leading / is stripped automatically.

SQLite hot-copy detection (sqliteBackupDirs)

Half of selfhosted runs on SQLite (vaultwarden, the arrs, Home Assistant, paperless), and the classic failure is rsync/cp of a *running app’s data dir: the copy "succeeds" daily and restores corrupt. SQLite keeps -wal/-shm files next to a database only while it is open — inside a backup directory they are proof the copy happened mid-write.

sqlite/hot-copy

RED

the backup directory contains WAL/SHM files — the database(s) next to them were copied while open and may not restore. Copy with sqlite3 <db> '.backup' / VACUUM INTO, or stop the app during the copy

ZFS snapshots & replication (zfsReplications)

Sanoid/syncoid-style setups (or any zfs send pipeline) on hosts outside TrueNAS: the source dataset must keep getting snapshots, and — when a replica is configured — the replica’s newest snapshot must be fresh too. Both fail quietly in real labs: the replica keeps its old snapshots and looks fine.

zfs-replication/no-snapshots

RED

the source dataset has no snapshots at all — the snapshot job never ran here

zfs-replication/snapshot-stale

RED

newest source snapshot is older than maxSnapshotAgeHours — sanoid/the cron job stopped

zfs-replication/replica-missing

RED

the replica dataset has no snapshots — nothing was ever replicated into it

zfs-replication/replica-stale

RED

newest replicated snapshot is older than maxReplicaAgeHours — replication stopped while the source kept going

TrueNAS (trueNas)

zfs/snapshot-stale

RED

newest ZFS snapshot is older than maxSnapshotAgeHours

cloudsync/failed

RED

a cloud-sync task’s last run failed

cloudsync/stale

RED

a cloud-sync task hasn’t succeeded within maxSyncAgeHours

cloudsync/disabled

YELLOW

a configured cloud-sync task is disabled

cloudsync/not-off-box

YELLOW

a top-level dataset is covered by no cloud-sync task — its data never leaves the box

Storage & disks (pveNodes, trueNas, smartHosts)

storage/unhealthy

RED

a pool/datastore is not healthy (e.g. DEGRADED) or a disk fails SMART

storage/capacity-critical

RED

usage above the critical threshold — backups may already be silently rejected

storage/capacity-warn

YELLOW

usage above the warning threshold

storage/scrub-overdue

YELLOW

last ZFS scrub is too old

storage/inactive

YELLOW

a configured storage is not active

storage/health-unknown

YELLOW

health could not be determined

Suppression hygiene (always on)

Suppressions themselves are audited — they can never rot silently:

suppression/expired

YELLOW

a suppression passed its expires date and needs a fresh decision

suppression/unknown-target

YELLOW

a suppression points at a host/service that no longer exists

These two are not suppressible — by design.