7.7 KiB
S3 Backup Bucket Audit Procedure
A repeatable methodology for auditing all Wasabi S3 backup buckets — versioning status, file counts, total sizes, staleness checks, and fresh-object verification.
Why audit
- Catch backup pipeline failures early (the MikroTik router backup pipeline stopped producing configs after Jul 5, 2026, and went unnoticed for 3 days)
- Verify the live-sync checkpoint is actually producing objects from today
- Confirm versioning wasn't accidentally disabled during a bucket config change
- Know your total storage consumption for cost tracking
Prerequisites
# AWS CLI with Wasabi endpoint configured
/opt/awscli-venv/bin/aws configure # ~/.aws/credentials must exist
ENDPOINT="https://s3.us-east-1.wasabisys.com"
Quick audit script
Run this on all existing buckets at once:
ENDPOINT="https://s3.us-east-1.wasabisys.com"
for BUCKET in hermes-vps-backups itpropartner-backups mikrotik-ccr-backups itpropartner-system-configs itpropartner-docker-volumes; do
echo "=== $BUCKET ==="
# Versioning status
echo "--- Versioning ---"
/opt/awscli-venv/bin/aws s3api get-bucket-versioning --bucket "$BUCKET" --endpoint-url "$ENDPOINT" 2>&1
# Top-level layout and latest object
echo "--- Top-level listing (last 5 modified) ---"
/opt/awscli-venv/bin/aws s3 ls s3://"$BUCKET"/ --endpoint-url "$ENDPOINT" --human-readable --recursive 2>&1 | sort -k1,2r | head -5
# Summary (count + size)
/opt/awscli-venv/bin/aws s3 ls s3://"$BUCKET"/ --endpoint-url "$ENDPOINT" --human-readable --recursive --summarize 2>&1 | tail -2
# Staleness: count objects from today
TODAY=$(date +%F)
TODAY_COUNT=$(/opt/awscli-venv/bin/aws s3 ls s3://"$BUCKET"/ --endpoint-url "$ENDPOINT" --recursive 2>&1 | grep -c "$TODAY" || true)
echo "Objects from today ($TODAY): $TODAY_COUNT"
echo ""
done
Detailed per-bucket checks
1. Versioning
aws s3api get-bucket-versioning --bucket <name> --endpoint-url https://s3.us-east-1.wasabisys.com
Expected: {"Status": "Enabled", "MFADelete": "Disabled"}
If Status is absent or Suspended, the bucket is no longer protected against accidental overwrites/deletes.
2. File count and total size
aws s3 ls s3://<bucket>/ --endpoint-url <endpoint> --recursive --human-readable --summarize
The summary lines at the bottom give you:
Total Objects: NTotal Size: X.X GiB/MiB
3. Staleness check (per top-level prefix)
Check whether any objects exist from today:
aws s3 ls s3://<bucket>/ --endpoint-url <endpoint> --recursive | grep "$(date +%F)" | sort -r | head -5
If zero objects from today, check yesterday:
aws s3 ls s3://<bucket>/ --endpoint-url <endpoint> --recursive | grep "$(date -d yesterday +%F)" | sort -r | head -5
Staleness thresholds:
| Type | Max acceptable gap |
|---|---|
| Live Hermes sync | < 30 minutes (expected every 15 min) |
| Full Hermes backup | < 48 hours (daily at 1 AM UTC) |
| Router config backup | < 48 hours (daily at 6 AM UTC) |
4. Live sync freshness (deep check for hermes-vps-backups)
The most recent object timestamp tells you when the 15-min live sync last ran:
aws s3 ls s3://hermes-vps-backups/live/ --endpoint-url https://s3.us-east-1.wasabisys.com --recursive 2>&1 | sort -k1,2r | head -5
Also count today's objects in the live prefix specifically:
aws s3 ls s3://hermes-vps-backups/live/ --endpoint-url https://s3.us-east-1.wasabisys.com --recursive 2>&1 | grep -c "$(date +%F)"
This should be a large number (hundreds to thousands) indicating the sync is actively pushing state changes.
5. Full backup archive check
aws s3 ls s3://hermes-vps-backups/hermes-full-backup/ --endpoint-url https://s3.us-east-1.wasabisys.com --human-readable
Check that a .tar.gz file exists from within the last 48 hours. Also check the manifest file is present beside each archive.
6. Caddyfile copy check
The live sync pushes /etc/caddy/Caddyfile to s3://hermes-vps-backups/live/caddy/Caddyfile every 15 min. Verify it's current:
aws s3 ls s3://hermes-vps-backups/live/caddy/ --endpoint-url https://s3.us-east-1.wasabisys.com
7. Empty purpose-specific bucket check
The itpropartner-system-configs and itpropartner-docker-volumes buckets exist on Wasabi but may contain 0 objects if their sync scripts are not running. Check both:
for BUCKET in itpropartner-system-configs itpropartner-docker-volumes; do
echo "=== $BUCKET ==="
/opt/awscli-venv/bin/aws s3 ls s3://"$BUCKET"/ --endpoint-url "$ENDPOINT" --recursive --summarize 2>&1 | tail -2
done
Expected: Total Objects: > 0. If 0 objects, the corresponding sync script (hermes-system-config-sync.sh or hermes-docker-sync.sh) is not running or failing silently.
Known bucket inventory (as of Jul 11, 2026)
| Bucket | Purpose | Status |
|---|---|---|
hermes-vps-backups |
Hermes full backup + live sync + standby | ✅ Versioning ON, active objects |
itpropartner-backups |
Portal/ITP backups | ✅ Versioning ON, minimal objects |
mikrotik-ccr-backups |
Router config exports | ✅ Versioning ON, stale |
itpropartner-system-configs |
System configs | 🟡 Bucket exists but EMPTY — sync script not running or failing |
itpropartner-docker-volumes |
Docker volume data | 🟡 Bucket exists but EMPTY — sync script not running or failing |
Common failure patterns
| Symptom | Likely cause | Fix |
|---|---|---|
Zero objects from today on live/ |
Live sync cron job stopped or credentials rotated | Check hermes cron list for hermes-live-sync status; verify ~/.aws/credentials |
Bucket listing returns objects but versioning says Suspended |
Someone ran put-bucket-versioning with Status=Suspended |
Re-enable: aws s3api put-bucket-versioning --bucket <name> --versioning-configuration Status=Enabled --endpoint-url <endpoint> |
| Full backup archive is >48h old | Daily cron job stopped running; live sync masks the gap | Check system crontab; run bash /root/.hermes/scripts/hermes-backup.sh manually; verify BACKUP_DIR not on /tmp |
| Router config backups stopped | VPN tunnel down, SSH key rotated, or pipeline script error | SSH to gateway CCR and test manually; check the tower config YAML |
AccessDenied on ListBuckets |
Expected — IAM policy scopes to specific bucket ARNs | Test by bucket name directly, not aws s3 ls (no arg) |
InvalidAccessKeyId |
Stale credentials in ~/.aws/credentials |
Get current key from password manager and update the file |
| Bucket exists but has 0 objects | Sync script not running or failing silently | Check system crontab for the sync job; run the script manually and check output; verify aws is in PATH (venv activation) |
s3api get-bucket-versioning returns empty |
Versioning not yet enabled on bucket or s3:GetBucketVersioning missing from IAM policy |
Enable with put-bucket-versioning; check IAM policy includes s3:GetBucketVersioning on the bucket ARN |
AWS CLI command not found in cron |
Backup/sync script missing venv activation | Add source /opt/awscli-venv/bin/activate near the top of the script (after set -euo pipefail, before first aws call) |
| Staleness >48h but watchdog didn't alert | Watchdog uses 36h threshold, so it reports OK for ~1.5 days after last successful backup | The watchdog is working as designed (grace period prevents false alarms). The real fix is preventing the backup from failing in the first place |
Periodic audit cadence
- Weekly: Run the quick audit script on existing buckets
- Monthly: Review full backup vs live sync size trends for cost tracking
- Post-migration: Run the full audit procedure immediately after any server migration or credential rotation