A backup you’ve never restored is a guess, not a plan.
Two independent durability stories: Postgres backs up nightly to infrastructure you control, and Redis — which holds the live send queue, not just a cache — is configured for crash-safe durability by default.
WarmHawk backs up Postgres nightly (pg_dump, gzip, your own local path or rclone remote — never WarmHawk’s infrastructure) with a full restore procedure below, and runs Redis with AOF durability (appendfsync everysec, noeviction) because Redis backs the real BullMQ send queue — losing unflushed writes there means a lead silently never gets emailed.
How nightly Postgres backups work
scripts/backup-postgres.sh runs on a nightly schedule inside your instance: pg_dump, piped through gzip, written to a destination you configured — a local path by default, or your own rclone remote (your own S3/B2 bucket) if you set one up during install. Enabled via a one-time install prompt (default yes), 14-day retention by default, configurable directly in the script.
API details (Tier 0 / self-hosters) · Run a backup right now, before a risky change▸
docker compose exec api /app/scripts/backup-postgres.sh
ls -lh /var/backups/warmhawk/Restoring from a backup
- Stop the services that write to Postgres.
API details (Tier 0 / self-hosters) · Stop dependent services▸
docker compose stop api worker - Restore the gzipped dump.Restoring onto a database with existing (possibly corrupted) data: drop and recreate it first so the restore starts clean.
API details (Tier 0 / self-hosters) · Restore from a gzipped dump▸
gunzip -c /var/backups/warmhawk/warmhawk-2026-08-20.sql.gz \ | docker compose exec -T postgres psql -U warmhawk -d warmhawk - Restart the API and worker.
API details (Tier 0 / self-hosters) · Bring services back up▸
docker compose start api worker - Verify it actually worked.
API details (Tier 0 / self-hosters) · Verify health, then spot-check data▸
docker compose ps docker compose exec postgres psql -U warmhawk -d warmhawk -c "SELECT count(*) FROM leads;"
Redis durability: why it isn’t just a cache
Redis backs the BullMQ dispatch queue — mailbox rotation, cadence/jitter scheduling, every job waiting to send. Losing unflushed writes on a crash or restart means a lead silently never gets emailed, with no error surfaced anywhere else in the system. That’s why the bundled ops/redis.conf configures real durability instead of Redis’s cache-friendly defaults:
API details (Tier 0 / self-hosters) · ops/redis.conf — the durability-relevant lines▸
appendonly yes
appendfsync everysec
aof-use-rdb-preamble yes
save 900 1
save 300 10
save 60 10000
maxmemory-policy noeviction- AOF, fsync every second — every write is logged to an append-only file, flushed at most once a second, so a crash loses at most a second of writes instead of everything since the last RDB snapshot.
- RDB snapshots as a second safety net, layered on top of AOF, not instead of it.
noeviction— Redis refuses new writes rather than silently evicting queue data under memory pressure. A refused write is loud and recoverable; a silently evicted job is neither.- A crash-recovery reconciliation cron cross-checks
ExecutionLogrows against what BullMQ actually has queued on every restart, and re-enqueues anything that looks lost — a second line of defense behind AOF, not a substitute for it.
Redis’s password (REDIS_PASSWORD) is passed via --requirepass on the command line rather than written into the conf file, so it never ends up committed anywhere — generated once by install.sh and stored only in your own .env.
A backup you’ve never restored is a guess, not a plan. Run the restore procedure above once against a spare box or a disposable instance — not production — so you know firsthand it works before an actual incident makes that the first time you’ve tried it.
Rolling back a bad update rather than a data problem? See warmhawk update failures — a rollback to the previous image tag may be all you need, without touching the database at all.
Questions
Backups & Redis durability: questions worth answering up front
Does WarmHawk ever receive or store a copy of my backups?+
No. Backups are written entirely to your own configured destination — a local path by default, or your own rclone-driven remote (your own S3/B2 bucket) if you set one up. WarmHawk never receives or stores a customer’s backup on any WarmHawk-operated infrastructure.
Why does Redis durability matter if Postgres is the real database?+
Redis backs the BullMQ dispatch queue itself, not just a cache — losing unflushed writes on a crash or restart means a lead silently never gets emailed, with nothing else in the system surfacing an error. That is why it runs AOF (append-only file), not the eviction-friendly defaults.
Should I actually test a restore before I need one?+
Yes, and not optionally. Run the restore steps below against a spare box or a disposable instance at least once so you know the exact commands work, before an actual incident forces you to learn them under pressure.
What happens to in-flight queue jobs if Redis crashes anyway?+
A crash-recovery reconciliation job cross-checks ExecutionLog rows against the queue on restart and re-enqueues anything that looks lost — a second line of defense behind AOF durability, not a replacement for it.