A successful HTTP check does not prove your backup ran
Your status endpoint can return 200 while a separate scheduled backup has not run. An HTTP uptime monitor answers one narrow question: can a client reach this endpoint and receive the expected response now? It says nothing about a scheduled process that creates, uploads, or retains a backup.
This gap appears when a scheduler stops, the backup command loses database access, a changed environment variable points at the wrong destination, or an upload fails after the dump completes. A green HTTP check and a missing backup can exist at the same time because they observe different work.
Put the heartbeat in the same process that runs the backup. A separate cron entry can run successfully even when the backup never starts. PostDeploy records a non-zero exit code as an explicit failed heartbeat. PostDeploy does not currently create a missed-run incident when no finish heartbeat arrives. Use an independent scheduler watchdog if you need that alert.
Report the backup command result
If your PostgreSQL job uses pg_dump, its custom archive format restores with pg_restore. The command below shows the reporting order. It does not prescribe a production backup design.
Set POSTDEPLOY_INGEST_URL to https://ingest.postdeploy.dev and store the token where the scheduler can read it. The token is a credential. Do not put it in source control or print it in logs.
The /0 path records a successful finish. A non-zero final path segment records a failed run. The wrapper preserves a failed backup exit status. When the backup succeeds but delivery fails, it exits with the curl status so the scheduler records the delivery failure. curl -f makes a 4xx or 5xx response fail the transfer.
#!/bin/sh
set -u
backup_file="/var/backups/app-$(date +%F).dump"
pg_dump --format=custom --file "$backup_file" "$PGDATABASE"
backup_status=$?
curl -fsS --max-time 10 \
"$POSTDEPLOY_INGEST_URL/h/$POSTDEPLOY_HEARTBEAT_TOKEN/$backup_status"
delivery_status=$?
if [ "$backup_status" -ne 0 ]; then
exit "$backup_status"
fi
exit "$delivery_status"
Put the success ping after the final required step
A successful dump file may still not be a usable backup if a later upload or encryption step fails. Send the success heartbeat after the last step that makes the backup available where you rely on it.
Warning: upload_backup and verify_backup_object below are placeholders. Replace them with commands that your backup system provides before you run this pattern. Do not place a success ping before the upload. Do not write backup_command; curl ... without preserving the exit status.
# Replace upload_backup and verify_backup_object with your own commands.
pg_dump --format=custom --file /tmp/app.dump "$PGDATABASE" &&
upload_backup /tmp/app.dump "backups/app-$(date +%F).dump" &&
verify_backup_object "backups/app-$(date +%F).dump"
backup_status=$?
curl -fsS --max-time 10 \
"$POSTDEPLOY_INGEST_URL/h/$POSTDEPLOY_HEARTBEAT_TOKEN/$backup_status"
delivery_status=$?
if [ "$backup_status" -ne 0 ]; then
exit "$backup_status"
fi
exit "$delivery_status"
A completed backup is not a recovery or missed-run test
A success heartbeat shows that the job reached the reporting line and the endpoint accepted that report. It cannot show that the backup still exists, has the expected contents, meets retention rules, or can restore your application.
PostDeploy has no independent deadline evaluator for a missing finish. A stopped scheduler, an absent first run, or a command that never reaches the reporting line creates no check or incident. Use an external scheduler watchdog where you need missed-run detection.
Run a restore test on a separate schedule and give it its own heartbeat. PostgreSQL documents pg_restore as the tool to rebuild a custom-format archive. The backup job and restore test should have separate evidence because a recent dump is not evidence of a successful recovery.
Test the reporting paths before relying on them
Run the real backup once and confirm that the completion report appears as a passing check. Then force a harmless failure in a non-production test and confirm that a non-zero exit creates the failed check you expect. Do not claim that stopping the scheduler creates a PostDeploy alert. These checks do not prove recovery until the restore test succeeds.
Keep the HTTP uptime monitor too. It protects the user-facing endpoint. Add the completion heartbeat because the backup is a different failure domain.
Questions
Does an HTTP uptime check prove my backup ran?
No. It proves that the monitored endpoint responded. A heartbeat sent after the backup completes reports a different job.
Should I send the heartbeat before or after upload?
Send it after the last required step. A dump that was never stored where you need it is not a successful backup.
Does a successful heartbeat prove I can restore?
No. Run a restore test separately and give it its own heartbeat.
What PostDeploy does not do here
- PostDeploy does not perform backup restore testing.
- PostDeploy does not inspect backup contents or retention.
- PostDeploy does not upload, encrypt, or store backups.
Sources
Links support the technical facts and product boundaries described above.
- PostgreSQL 18: pg_dump Checked
- PostgreSQL 18: SQL dump and restore Checked
- curl FAQ: HTTP errors and --fail Checked