Most backup failures are not discovered during the outage — they are discovered during the restore, when it is far too late. A backup job that exits zero every night can still be writing truncated archives, partial database dumps, or files silently altered by a botched sync. Checksums close that gap: they let you prove, cheaply and automatically, that what you stored is exactly what you wrote, months before you need it.
What checksums verify — and what they do not
A checksum proves that the bytes you read back match the bytes you wrote. It proves integrity. It does not prove that the backup contains the data you expect, that the schema is complete, or that the restore process works. Checksums catch corruption and truncation; they miss logical errors. You need both checksum verification and a periodic restore drill. Use checksums on every backup, restores on a schedule.
| Failure mode | Checksum catches it? | Restore drill catches it? |
|---|---|---|
| Bit rot on storage | Yes | Yes |
| Truncated archive | Yes | Yes |
| Partial database dump | Yes (with dump hash) | Yes |
| Empty or wrong directory backed up | No | Yes |
| Missing table or column | No | Yes |
| Wrong encryption key | Yes (decrypt fails) | Yes |
Generating and storing manifests
The pattern is simple: hash the artifact at creation time, store the hash somewhere independent of the artifact, and re-verify before every restore. Store the manifest on the same host only if you also replicate it off-host — a checksum on the machine you are about to lose is worthless.
# create artifact and manifest together
D=$(date +%F)
tar czf /backups/www-$D.tar.gz -C /var/www html
sha256sum /backups/www-$D.tar.gz >> /backups/manifest-$D.txt
# database dump with its own hash
mysqldump --single-transaction --routines db | gzip > /backups/db-$D.sql.gz
sha256sum /backups/db-$D.sql.gz >> /backups/manifest-$D.txt
# ship both offsite (restic handles this natively)
restic backup /backups/www-$D.tar.gz /backups/db-$D.sql.gz /backups/manifest-$D.txt
A signed manifest is better than an unsigned one. If you have a GPG key, sign the manifest so a corrupted or tampered manifest cannot silently bless a corrupted artifact. For most single-server setups an unsigned manifest on separate storage is sufficient, but signing costs one extra line.
gpg --detach-sign --armor /backups/manifest-$D.txt
# verify later
gpg --verify /backups/manifest-$D.txt.asc /backups/manifest-$D.txt
Verification: where people get it wrong
Running sha256sum -c while the artifact sits on the same disk that produced it proves almost nothing — a single disk-level corruption affects both files equally. Verification must happen either after reading back from the offsite copy, or against a hash that was transmitted and stored independently.
# correct: verify the copy that was pulled back from offsite
restic restore latest --target /tmp/verify
cd /backups && sha256sum -c /tmp/verify/backups/manifest-$D.txt
# verify a restic repository's own integrity
restic check --read-data-subset=10%
restic check --read-data-subset is the pragma for the common case: reading a percentage of the repository each run keeps verification cost bounded while still exercising the actual stored data rather than just metadata. Over a month of daily runs at 10%, every byte of the repository gets re-read and hashed at least once.
Automating verification without alert fatigue
- Daily: verify the manifest of the most recent artifact after upload. Alert only on failure.
- Weekly: run a full
restic checkon repository metadata. - Monthly: pull one artifact back from offsite storage and verify against its manifest.
- Quarterly: perform an actual restore into a scratch environment and start the service.
Wire the daily check into the backup script itself so verification is impossible to skip, and make failures loud. A checksum mismatch on a fresh backup is a five-alarm event: it means either the source data is changing under you or the storage layer is corrupting writes. Both need immediate investigation before the next run.
#!/bin/bash
set -euo pipefail
D=$(date +%F)
sha256sum -c /backups/manifest-$D.txt || {
logger -t backup "CHECKSUM FAILURE $D"
mail -s "BACKUP CHECKSUM FAILURE $D" [email protected] <<< "Manifest verification failed."
exit 1
}
logger -t backup "verified $D"
Choosing the checksum algorithm
SHA-256 is the sensible default: fast on modern CPUs, universally available, and strong enough that collisions are not a concern. MD5 is faster but broken for adversarial use; treat it as a legacy compatibility option only. For very large datasets where hashing time matters more than collision resistance, xxHash or BLAKE3 through a dedicated tool performs far better, at the cost of a non-standard utility on every host that reads the manifest.
Verification is cheap insurance, and the whole discipline is three commands and a cron entry. The expensive part is discovering your backups were broken during an incident; the cheap part is a manifest and a weekly check. Pair it with off-host storage you can actually restore from, and the checksum becomes the proof that the last line of defense still holds. If you are designing the backup tier around storage constraints, review our VPS plans to size the offsite and staging capacity correctly before the first restore attempt, and see how managed versus unmanaged options change who keeps the restore drill honest.
Verification for object storage and immutable backups
Object storage adds ETags and content-MD5 headers you can compare against your own manifest. Treat the provider’s checksum as a second opinion, not a replacement: it proves the object arrived intact at the store, while your manifest proves the artifact was correct before it left the server. When both agree, you have verified the entire chain. When they disagree, the divergence point tells you whether corruption happened in transit or at rest — which is exactly the information you need to fix the pipeline rather than just re-run it.


Leave a Reply
You must be logged in to post a comment.