Backup Job Succeeded but the File Is Empty
Short answer
A backup job can exit with code 0 and still produce a useless artifact. This happens when the dump command fails silently, credentials are missing, stderr is ignored, or a file is created by redirection before any real data is written to it. A successful exit code tells you the process ran — it does not tell you the backup is restorable.
Why empty backups happen
- Command not found under cron’s PATH.
mysqldump,pg_dump, or similar tools installed via a package manager or version manager may not be on cron’s minimalPATH, so the command silently no-ops or the shell swallows the “not found” error. - Database credentials missing or expired. A
.pgpassor.my.cnffile readable in your interactive shell may not be readable by the user cron runs as. - Permission denied on the destination. The backup directory or destination volume may be unwritable for the cron user, and the error goes to a log nobody reads.
- Wrong working directory. Relative paths resolve differently under cron, so the dump can land somewhere other than where you expect — or fail to open its input file at all.
- Filesystem full. A dump can start, fill remaining disk, and truncate without cron reporting a nonzero exit code.
- Dump error is written to stderr, not evaluated. A shell pipeline like
mysqldump ... | gzip > backup.sql.gzmasks the exit status ofmysqldump— gzip on an empty stream still exits0. - Compression pipeline masks failure. Similar to above: a broken pipe or truncated input can still produce a well-formed but empty compressed file.
- File created before the command fails.
> backup.sqltruncates and creates the destination file immediately, before the dump command even runs. If the command then fails, you’re left with a 0-byte file and exit code0from the shell redirection.
Minimum backup checks
Before trusting a backup, verify:
- File exists at the expected path and filename pattern.
- File size is above a sane threshold — not just non-zero, but consistent with the size of prior backups.
- Modified within the expected window — a stale backup from three days ago passing an “exists” check is not a passing backup.
- Compression integrity —
gzip -tor equivalent passes without error. - Dump has the expected header or format — a SQL dump should start with the expected
-- MySQL dumporPGDMPheader, not be empty or truncated mid-statement. - Periodic restore test — the strongest guarantee is restoring the backup to a scratch database and running a basic query against it.
A better pattern
# Write to a temp file first
pg_dump mydb > /backups/mydb.sql.tmp
# Validate before promoting
if [ -s /backups/mydb.sql.tmp ] && head -c 5 /backups/mydb.sql.tmp | grep -q "PGDMP\|--"; then
mv /backups/mydb.sql.tmp /backups/mydb.sql
else
echo "backup validation failed: empty or malformed dump" >&2
exit 1
fi
The core idea: never let an unvalidated file overwrite your last known-good backup. Validate first, promote (move/rename) second, and alert loudly if validation fails.
Where Night Watchman fits
Night Watchman is built for exactly this failure mode in file-artifact backup jobs. Instead of trusting the cron process’s exit code, it checks the backup artifact itself after the job runs — file existence, freshness, size relative to history, and format — and escalates an evidence dossier (file path, byte count, timestamp, and what was expected vs. observed) rather than a bare pass/fail. It does not auto-fix or re-run your backup job; it surfaces what it found so a human can decide.
FAQ
Why does my backup job show success but the file is empty?
Because “success” usually means the shell process exited with code 0, which only reflects the last command in a pipeline or the outer script — not whether the actual dump command produced valid data.
How do I check if a backup is restorable without doing a full restore?
At minimum check file size against history and confirm the file’s header matches the expected dump format. For real confidence, periodically restore to a scratch database.
Should I alert on backup size instead of just existence?
Yes. A file that exists but is 10x smaller than yesterday’s backup is a stronger warning sign than a missing file, and it’s easy to miss without a size check.
Related pages
- What is a silent cron failure?
- Script works manually but not in cron
- Exit code 0 is not a health check
Try Night Watchman
Install from the public repository:
curl -fsSL https://raw.githubusercontent.com/ssddee-dev/code-batman/main/install.sh | bash
cd ~/night-watchman
.venv/bin/python -m watchman.setup